ZLPhotoPreviewPopInteractiveTransition.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. //
  2. // ZLPhotoPreviewPopInteractiveTransition.swift
  3. // ZLPhotoBrowser
  4. //
  5. // Created by long on 2020/9/3.
  6. //
  7. // Copyright (c) 2020 Long Zhang <495181165@qq.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import UIKit
  27. class ZLPhotoPreviewPopInteractiveTransition: UIPercentDrivenInteractiveTransition {
  28. weak var transitionContext: UIViewControllerContextTransitioning?
  29. weak var viewController: ZLPhotoPreviewController?
  30. var shadowView: UIView?
  31. var imageView: UIImageView?
  32. var imageViewOriginalFrame: CGRect = .zero
  33. var startPanPoint: CGPoint = .zero
  34. var interactive: Bool = false
  35. var shouldStartTransition: ( (CGPoint) -> Bool )?
  36. var startTransition: ( () -> Void )?
  37. var cancelTransition: ( () -> Void )?
  38. var finishTransition: ( () -> Void )?
  39. init(viewController: ZLPhotoPreviewController) {
  40. super.init()
  41. self.viewController = viewController
  42. let dismissPan = UIPanGestureRecognizer(target: self, action: #selector(dismissPanAction(_:)))
  43. viewController.view.addGestureRecognizer(dismissPan)
  44. }
  45. @objc func dismissPanAction(_ pan: UIPanGestureRecognizer) {
  46. let point = pan.location(in: self.viewController?.view)
  47. if pan.state == .began {
  48. guard self.shouldStartTransition?(point) == true else {
  49. self.interactive = false
  50. return
  51. }
  52. self.startPanPoint = point
  53. self.interactive = true
  54. self.startTransition?()
  55. self.viewController?.navigationController?.popViewController(animated: true)
  56. } else if pan.state == .changed {
  57. guard self.interactive else {
  58. return
  59. }
  60. let result = self.panResult(pan)
  61. self.imageView?.frame = result.frame
  62. self.shadowView?.alpha = pow(result.scale, 2)
  63. self.update(result.scale)
  64. } else if pan.state == .cancelled || pan.state == .ended {
  65. guard self.interactive else {
  66. return
  67. }
  68. let vel = pan.velocity(in: self.viewController?.view)
  69. let p = pan.translation(in: self.viewController?.view)
  70. let percent: CGFloat = max(0.0, p.y / (self.viewController?.view.bounds.height ?? UIScreen.main.bounds.height))
  71. let dismiss = vel.y > 300 || (percent > 0.2 && vel.y > -300)
  72. if dismiss {
  73. self.finish()
  74. self.finishAnimate()
  75. } else {
  76. self.cancel()
  77. self.cancelAnimate()
  78. }
  79. self.imageViewOriginalFrame = .zero
  80. self.startPanPoint = .zero
  81. self.interactive = false
  82. }
  83. }
  84. func panResult(_ pan: UIPanGestureRecognizer) -> (frame: CGRect, scale: CGFloat) {
  85. // 拖动偏移量
  86. let translation = pan.translation(in: self.viewController?.view)
  87. let currentTouch = pan.location(in: self.viewController?.view)
  88. // 由下拉的偏移值决定缩放比例,越往下偏移,缩得越小。scale值区间[0.3, 1.0]
  89. let scale = min(1.0, max(0.3, 1 - translation.y / UIScreen.main.bounds.height))
  90. let width = self.imageViewOriginalFrame.size.width * scale
  91. let height = self.imageViewOriginalFrame.size.height * scale
  92. // 计算x和y。保持手指在图片上的相对位置不变。
  93. let xRate = (self.startPanPoint.x - self.imageViewOriginalFrame.origin.x) / self.imageViewOriginalFrame.size.width
  94. let currentTouchDeltaX = xRate * width
  95. let x = currentTouch.x - currentTouchDeltaX
  96. let yRate = (self.startPanPoint.y - self.imageViewOriginalFrame.origin.y) / self.imageViewOriginalFrame.size.height
  97. let currentTouchDeltaY = yRate * height
  98. let y = currentTouch.y - currentTouchDeltaY
  99. return (CGRect(x: x.isNaN ? 0 : x, y: y.isNaN ? 0 : y, width: width, height: height), scale)
  100. }
  101. override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
  102. self.transitionContext = transitionContext
  103. self.startAnimate()
  104. }
  105. func startAnimate() {
  106. guard let context = self.transitionContext else {
  107. return
  108. }
  109. guard let fromVC = context.viewController(forKey: .from) as? ZLPhotoPreviewController, let toVC = context.viewController(forKey: .to) as? ZLThumbnailViewController else {
  110. return
  111. }
  112. let containerView = context.containerView
  113. containerView.addSubview(toVC.view)
  114. self.shadowView = UIView(frame: containerView.bounds)
  115. self.shadowView?.backgroundColor = UIColor.black
  116. containerView.addSubview(self.shadowView!)
  117. let cell = fromVC.collectionView.cellForItem(at: IndexPath(row: fromVC.currentIndex, section: 0)) as! ZLPreviewBaseCell
  118. let fromImageViewFrame = cell.animateImageFrame(convertTo: containerView)
  119. self.imageView = UIImageView(frame: fromImageViewFrame)
  120. self.imageView?.contentMode = .scaleAspectFill
  121. self.imageView?.clipsToBounds = true
  122. self.imageView?.image = cell.currentImage
  123. containerView.addSubview(self.imageView!)
  124. self.imageViewOriginalFrame = self.imageView!.frame
  125. }
  126. func finishAnimate() {
  127. guard let context = self.transitionContext else {
  128. return
  129. }
  130. guard let fromVC = context.viewController(forKey: .from) as? ZLPhotoPreviewController, let toVC = context.viewController(forKey: .to) as? ZLThumbnailViewController else {
  131. return
  132. }
  133. let fromVCModel = fromVC.arrDataSources[fromVC.currentIndex]
  134. let toVCVisiableIndexPaths = toVC.collectionView.indexPathsForVisibleItems
  135. var diff = 0
  136. if toVC.showCameraCell, !ZLPhotoConfiguration.default().sortAscending {
  137. diff = -1
  138. }
  139. var toIndex: Int? = nil
  140. for indexPath in toVCVisiableIndexPaths {
  141. let idx = indexPath.row + diff
  142. if idx >= toVC.arrDataSources.count || idx < 0 {
  143. continue
  144. }
  145. let m = toVC.arrDataSources[idx]
  146. if m == fromVCModel {
  147. toIndex = indexPath.row
  148. break
  149. }
  150. }
  151. var toFrame: CGRect? = nil
  152. if let toIdx = toIndex, let toCell = toVC.collectionView.cellForItem(at: IndexPath(row: toIdx, section: 0)) {
  153. toFrame = toVC.collectionView.convert(toCell.frame, to: context.containerView)
  154. }
  155. UIView.animate(withDuration: 0.25, animations: {
  156. if let to = toFrame {
  157. self.imageView?.frame = to
  158. } else {
  159. self.imageView?.alpha = 0
  160. }
  161. self.shadowView?.alpha = 0
  162. }) { (_) in
  163. self.imageView?.removeFromSuperview()
  164. self.shadowView?.removeFromSuperview()
  165. self.imageView = nil
  166. self.shadowView = nil
  167. self.finishTransition?()
  168. context.completeTransition(!context.transitionWasCancelled)
  169. }
  170. }
  171. func cancelAnimate() {
  172. guard let context = self.transitionContext else {
  173. return
  174. }
  175. UIView.animate(withDuration: 0.25, animations: {
  176. self.imageView?.frame = self.imageViewOriginalFrame
  177. self.shadowView?.alpha = 1
  178. }) { (_) in
  179. self.imageView?.removeFromSuperview()
  180. self.shadowView?.removeFromSuperview()
  181. self.cancelTransition?()
  182. context.completeTransition(!context.transitionWasCancelled)
  183. }
  184. }
  185. }