PagingPhotoView.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // Copyright 2020 Google LLC. All rights reserved.
  2. //
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  5. // file except in compliance with the License. You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software distributed under
  10. // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  11. // ANY KIND, either express or implied. See the License for the specific language governing
  12. // permissions and limitations under the License.
  13. import GooglePlaces
  14. import UIKit
  15. struct AttributedPhoto {
  16. var image: UIImage
  17. var attributions: NSAttributedString
  18. }
  19. /// Represents a place photo, along with the attributions which are required to be displayed along
  20. /// with it.
  21. private class ImageAndAttributionView: UIView {
  22. private let margin: CGFloat = 30
  23. private let textViewHeight: CGFloat = 50
  24. lazy var imageView: UIImageView = {
  25. let imageView = UIImageView()
  26. imageView.contentMode = .scaleAspectFill
  27. imageView.clipsToBounds = true
  28. imageView.layer.cornerRadius = 10
  29. imageView.translatesAutoresizingMaskIntoConstraints = false
  30. return imageView
  31. }()
  32. lazy var attributionView: UITextView = {
  33. let textView = UITextView()
  34. textView.delegate = self
  35. textView.isScrollEnabled = false
  36. textView.translatesAutoresizingMaskIntoConstraints = false
  37. return textView
  38. }()
  39. init(attributedPhoto: AttributedPhoto) {
  40. super.init(frame: .zero)
  41. addSubview(imageView)
  42. NSLayoutConstraint.activate([
  43. imageView.topAnchor.constraint(equalTo: topAnchor, constant: margin),
  44. imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: margin),
  45. imageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -margin),
  46. ])
  47. addSubview(attributionView)
  48. NSLayoutConstraint.activate([
  49. attributionView.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: margin),
  50. attributionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -margin),
  51. attributionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: margin),
  52. attributionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -margin),
  53. attributionView.heightAnchor.constraint(equalToConstant: textViewHeight),
  54. ])
  55. imageView.image = attributedPhoto.image
  56. attributionView.attributedText = attributedPhoto.attributions
  57. }
  58. required init?(coder aDecoder: NSCoder) {
  59. fatalError("init(coder:) has not been implemented")
  60. }
  61. }
  62. extension ImageAndAttributionView: UITextViewDelegate {
  63. // Make links clickable.
  64. func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange)
  65. -> Bool
  66. {
  67. return true
  68. }
  69. }
  70. /// A horizontally-paging scroll view that displays a list of photo images and their attributions.
  71. @objc(PagingPhotoView)
  72. class PagingPhotoView: UIScrollView {
  73. private var pageViews: [ImageAndAttributionView] = []
  74. private var contentView = UIView()
  75. public var shouldRedraw = false
  76. func updatePhotos(_ photoList: [AttributedPhoto]) {
  77. // Reset state of pageViews and contentView
  78. pageViews = []
  79. contentView.removeFromSuperview()
  80. contentView = UIView()
  81. isPagingEnabled = true
  82. addSubview(contentView)
  83. // Generate page views, then add them to the scroll view's content view.
  84. for (index, photo) in photoList.enumerated() {
  85. let pageView = ImageAndAttributionView(attributedPhoto: photo)
  86. pageView.frame = CGRect(
  87. x: CGFloat(index) * frame.size.width, y: 0, width: frame.size.width,
  88. height: frame.size.height)
  89. pageViews.append(pageView)
  90. contentView.addSubview(pageView)
  91. }
  92. contentView.translatesAutoresizingMaskIntoConstraints = false
  93. NSLayoutConstraint.activate([
  94. contentView.topAnchor.constraint(equalTo: topAnchor),
  95. contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
  96. contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
  97. contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
  98. contentView.widthAnchor.constraint(
  99. equalToConstant: bounds.width * CGFloat(pageViews.count)),
  100. contentView.centerYAnchor.constraint(equalTo: centerYAnchor),
  101. ])
  102. }
  103. override func layoutSubviews() {
  104. super.layoutSubviews()
  105. guard shouldRedraw else { return }
  106. shouldRedraw = false
  107. // Update `ImageAndAttributionView` frame after rotation.
  108. for (index, photoView) in pageViews.enumerated() {
  109. photoView.frame = CGRect(
  110. x: CGFloat(index) * frame.size.width, y: 0, width: frame.size.width,
  111. height: frame.size.height)
  112. }
  113. // Update contentSize.
  114. let originWidth = contentSize.width
  115. contentSize = CGSize(
  116. width: CGFloat(pageViews.count) * frame.size.width, height: frame.size.height)
  117. // Re-adjust the content offset to ensure the photos are aligned properly horizontally.
  118. if contentSize.width != 0 {
  119. let scrollOffset = (CGFloat)(round((contentOffset.x / originWidth) * 10) / 10)
  120. contentOffset = CGPoint(x: scrollOffset * contentSize.width, y: 0)
  121. }
  122. }
  123. }