NCViewerPhotoImageScrollView.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import UIKit
  2. class NCViewerPhotoImageScrollView: UIScrollView, UIScrollViewDelegate {
  3. var index: Int!
  4. var zoomView: UIImageView!
  5. var tilingView: NCViewerPhotoTilingView?
  6. lazy var zoomingTap: UITapGestureRecognizer = {
  7. let zoomingTap = UITapGestureRecognizer(target: self, action: #selector(handleZoomingTap(_:)))
  8. zoomingTap.numberOfTapsRequired = 2
  9. return zoomingTap
  10. }()
  11. override init(frame: CGRect) {
  12. super.init(frame: frame)
  13. self.showsHorizontalScrollIndicator = false
  14. self.showsVerticalScrollIndicator = false
  15. self.decelerationRate = UIScrollView.DecelerationRate.fast
  16. self.delegate = self
  17. }
  18. required init?(coder aDecoder: NSCoder) {
  19. fatalError("init(coder:) has not been implemented")
  20. }
  21. override func layoutSubviews() {
  22. super.layoutSubviews()
  23. self.centerImage()
  24. }
  25. //MARK: - Configure scrollView to display new image
  26. func display(_ image: UIImage) {
  27. //1. clear the previous image
  28. zoomView?.removeFromSuperview()
  29. zoomView = nil
  30. //2. make a new UIImageView for the new image
  31. zoomView = UIImageView(image: image)
  32. self.addSubview(zoomView)
  33. self.configureFor(image.size)
  34. }
  35. func displayTiledImage(in url: URL, size imageSize: CGSize) {
  36. // clear views for the previous image
  37. zoomView?.removeFromSuperview()
  38. zoomView = nil
  39. tilingView = nil
  40. // make views to display the new image
  41. zoomView = UIImageView(frame: CGRect(origin: CGPoint.zero, size: imageSize))
  42. let image = placeholderImage(in: url)
  43. zoomView.image = image
  44. self.addSubview(zoomView)
  45. self.tilingView = NCViewerPhotoTilingView(in: url, size: imageSize)
  46. self.zoomView?.addSubview(self.tilingView!)
  47. self.configureFor(imageSize)
  48. }
  49. func configureFor(_ imageSize: CGSize) {
  50. self.contentSize = imageSize
  51. self.setMaxMinZoomScaleForCurrentBounds()
  52. self.zoomScale = self.minimumZoomScale
  53. //Enable zoom tap
  54. self.zoomView.addGestureRecognizer(self.zoomingTap)
  55. self.zoomView.isUserInteractionEnabled = true
  56. }
  57. func setMaxMinZoomScaleForCurrentBounds() {
  58. let boundsSize = self.bounds.size
  59. let imageSize = zoomView.bounds.size
  60. //1. calculate minimumZoomscale
  61. let xScale = boundsSize.width / imageSize.width // the scale needed to perfectly fit the image width-wise
  62. let yScale = boundsSize.height / imageSize.height // the scale needed to perfectly fit the image height-wise
  63. let minScale = min(xScale, yScale) // use minimum of these to allow the image to become fully visible
  64. //2. calculate maximumZoomscale
  65. var maxScale: CGFloat = 1.0
  66. if minScale < 0.1 {
  67. maxScale = 0.3
  68. }
  69. if minScale >= 0.1 && minScale < 0.5 {
  70. maxScale = 0.7
  71. }
  72. if minScale >= 0.5 {
  73. maxScale = max(1.0, minScale)
  74. }
  75. self.maximumZoomScale = maxScale
  76. self.minimumZoomScale = minScale
  77. }
  78. func centerImage() {
  79. // center the zoom view as it becomes smaller than the size of the screen
  80. let boundsSize = self.bounds.size
  81. var frameToCenter = zoomView?.frame ?? CGRect.zero
  82. // center horizontally
  83. if frameToCenter.size.width < boundsSize.width {
  84. frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width)/2
  85. }
  86. else {
  87. frameToCenter.origin.x = 0
  88. }
  89. // center vertically
  90. if frameToCenter.size.height < boundsSize.height {
  91. frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height)/2
  92. }
  93. else {
  94. frameToCenter.origin.y = 0
  95. }
  96. zoomView?.frame = frameToCenter
  97. }
  98. //MARK: - UIScrollView Delegate Methods
  99. func viewForZooming(in scrollView: UIScrollView) -> UIView? {
  100. return self.zoomView
  101. }
  102. func scrollViewDidZoom(_ scrollView: UIScrollView) {
  103. self.centerImage()
  104. }
  105. //MARK: - Methods called during rotation to preserve the zoomScale and the visible portion of the image
  106. // returns the center point, in image coordinate space, to try restore after rotation.
  107. func pointToCenterAfterRotation() -> CGPoint {
  108. let boundsCenter = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
  109. return self.convert(boundsCenter, to: zoomView)
  110. }
  111. // returns the zoom scale to attempt to restore after rotation.
  112. func scaleToRestoreAfterRotation() -> CGFloat {
  113. var contentScale = self.zoomScale
  114. // If we're at the minimum zoom scale, preserve that by returning 0, which will be converted to the minimum
  115. // allowable scale when the scale is restored.
  116. if contentScale <= self.minimumZoomScale + CGFloat.ulpOfOne {
  117. contentScale = 0
  118. }
  119. return contentScale
  120. }
  121. func maximumContentOffset() -> CGPoint {
  122. let contentSize = self.contentSize
  123. let boundSize = self.bounds.size
  124. return CGPoint(x: contentSize.width - boundSize.width, y: contentSize.height - boundSize.height)
  125. }
  126. func minimumContentOffset() -> CGPoint {
  127. return CGPoint.zero
  128. }
  129. func restoreCenterPoint(to oldCenter: CGPoint, oldScale: CGFloat) {
  130. // Step 1: restore zoom scale, first making sure it is within the allowable range.
  131. self.zoomScale = min(self.maximumZoomScale, max(self.minimumZoomScale, oldScale))
  132. // Step 2: restore center point, first making sure it is within the allowable range.
  133. // 2a: convert our desired center point back to our own coordinate space
  134. let boundsCenter = self.convert(oldCenter, from: zoomView)
  135. // 2b: calculate the content offset that would yield that center point
  136. var offset = CGPoint(x: boundsCenter.x - self.bounds.size.width/2.0, y: boundsCenter.y - self.bounds.size.height/2.0)
  137. // 2c: restore offset, adjusted to be within the allowable range
  138. let maxOffset = self.maximumContentOffset()
  139. let minOffset = self.minimumContentOffset()
  140. offset.x = max(minOffset.x, min(maxOffset.x, offset.x))
  141. offset.y = max(minOffset.y, min(maxOffset.y, offset.y))
  142. self.contentOffset = offset
  143. }
  144. //MARK: - Handle ZoomTap
  145. @objc func handleZoomingTap(_ sender: UITapGestureRecognizer) {
  146. let location = sender.location(in: sender.view)
  147. self.zoom(to: location, animated: true)
  148. }
  149. func zoom(to point: CGPoint, animated: Bool) {
  150. let currentScale = self.zoomScale
  151. let minScale = self.minimumZoomScale
  152. let maxScale = self.maximumZoomScale
  153. if (minScale == maxScale && minScale > 1) {
  154. return;
  155. }
  156. let toScale = maxScale
  157. let finalScale = (currentScale == minScale) ? toScale : minScale
  158. let zoomRect = self.zoomRect(for: finalScale, withCenter: point)
  159. self.zoom(to: zoomRect, animated: animated)
  160. }
  161. // The center should be in the imageView's coordinates
  162. func zoomRect(for scale: CGFloat, withCenter center: CGPoint) -> CGRect {
  163. var zoomRect = CGRect.zero
  164. let bounds = self.bounds
  165. // the zoom rect is in the content view's coordinates.
  166. //At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
  167. //As the zoom scale decreases, so more content is visible, the size of the rect grows.
  168. zoomRect.size.width = bounds.size.width / scale
  169. zoomRect.size.height = bounds.size.height / scale
  170. // choose an origin so as to get the right center.
  171. zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0)
  172. zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0)
  173. return zoomRect
  174. }
  175. func placeholderImage(in url: URL) -> UIImage? {
  176. let name = url.deletingPathExtension().lastPathComponent
  177. let imageName = "\(name)_Placeholder.jpg"
  178. let url = url.appendingPathComponent(imageName)
  179. return UIImage(contentsOfFile: url.path)
  180. }
  181. }