| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- //
- // ZLPhotoPreviewPopInteractiveTransition.swift
- // ZLPhotoBrowser
- //
- // Created by long on 2020/9/3.
- //
- // Copyright (c) 2020 Long Zhang <495181165@qq.com>
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- import UIKit
- class ZLPhotoPreviewPopInteractiveTransition: UIPercentDrivenInteractiveTransition {
-
- weak var transitionContext: UIViewControllerContextTransitioning?
-
- weak var viewController: ZLPhotoPreviewController?
-
- var shadowView: UIView?
-
- var imageView: UIImageView?
-
- var imageViewOriginalFrame: CGRect = .zero
-
- var startPanPoint: CGPoint = .zero
-
- var interactive: Bool = false
-
- var shouldStartTransition: ( (CGPoint) -> Bool )?
-
- var startTransition: ( () -> Void )?
-
- var cancelTransition: ( () -> Void )?
-
- var finishTransition: ( () -> Void )?
-
- init(viewController: ZLPhotoPreviewController) {
- super.init()
- self.viewController = viewController
- let dismissPan = UIPanGestureRecognizer(target: self, action: #selector(dismissPanAction(_:)))
- viewController.view.addGestureRecognizer(dismissPan)
- }
-
- @objc func dismissPanAction(_ pan: UIPanGestureRecognizer) {
- let point = pan.location(in: self.viewController?.view)
-
- if pan.state == .began {
- guard self.shouldStartTransition?(point) == true else {
- self.interactive = false
- return
- }
- self.startPanPoint = point
- self.interactive = true
- self.startTransition?()
- self.viewController?.navigationController?.popViewController(animated: true)
- } else if pan.state == .changed {
- guard self.interactive else {
- return
- }
- let result = self.panResult(pan)
- self.imageView?.frame = result.frame
- self.shadowView?.alpha = pow(result.scale, 2)
-
- self.update(result.scale)
- } else if pan.state == .cancelled || pan.state == .ended {
- guard self.interactive else {
- return
- }
-
- let vel = pan.velocity(in: self.viewController?.view)
- let p = pan.translation(in: self.viewController?.view)
- let percent: CGFloat = max(0.0, p.y / (self.viewController?.view.bounds.height ?? UIScreen.main.bounds.height))
-
- let dismiss = vel.y > 300 || (percent > 0.2 && vel.y > -300)
-
- if dismiss {
- self.finish()
- self.finishAnimate()
- } else {
- self.cancel()
- self.cancelAnimate()
- }
- self.imageViewOriginalFrame = .zero
- self.startPanPoint = .zero
- self.interactive = false
- }
- }
-
- func panResult(_ pan: UIPanGestureRecognizer) -> (frame: CGRect, scale: CGFloat) {
- // 拖动偏移量
- let translation = pan.translation(in: self.viewController?.view)
- let currentTouch = pan.location(in: self.viewController?.view)
-
- // 由下拉的偏移值决定缩放比例,越往下偏移,缩得越小。scale值区间[0.3, 1.0]
- let scale = min(1.0, max(0.3, 1 - translation.y / UIScreen.main.bounds.height))
-
- let width = self.imageViewOriginalFrame.size.width * scale
- let height = self.imageViewOriginalFrame.size.height * scale
-
- // 计算x和y。保持手指在图片上的相对位置不变。
- let xRate = (self.startPanPoint.x - self.imageViewOriginalFrame.origin.x) / self.imageViewOriginalFrame.size.width
- let currentTouchDeltaX = xRate * width
- let x = currentTouch.x - currentTouchDeltaX
-
- let yRate = (self.startPanPoint.y - self.imageViewOriginalFrame.origin.y) / self.imageViewOriginalFrame.size.height
- let currentTouchDeltaY = yRate * height
- let y = currentTouch.y - currentTouchDeltaY
-
- return (CGRect(x: x.isNaN ? 0 : x, y: y.isNaN ? 0 : y, width: width, height: height), scale)
- }
-
- override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
- self.transitionContext = transitionContext
- self.startAnimate()
- }
-
- func startAnimate() {
- guard let context = self.transitionContext else {
- return
- }
- guard let fromVC = context.viewController(forKey: .from) as? ZLPhotoPreviewController, let toVC = context.viewController(forKey: .to) as? ZLThumbnailViewController else {
- return
- }
- let containerView = context.containerView
-
- containerView.addSubview(toVC.view)
-
- self.shadowView = UIView(frame: containerView.bounds)
- self.shadowView?.backgroundColor = UIColor.black
- containerView.addSubview(self.shadowView!)
-
- let cell = fromVC.collectionView.cellForItem(at: IndexPath(row: fromVC.currentIndex, section: 0)) as! ZLPreviewBaseCell
-
- let fromImageViewFrame = cell.animateImageFrame(convertTo: containerView)
-
- self.imageView = UIImageView(frame: fromImageViewFrame)
- self.imageView?.contentMode = .scaleAspectFill
- self.imageView?.clipsToBounds = true
- self.imageView?.image = cell.currentImage
- containerView.addSubview(self.imageView!)
-
- self.imageViewOriginalFrame = self.imageView!.frame
- }
-
- func finishAnimate() {
- guard let context = self.transitionContext else {
- return
- }
- guard let fromVC = context.viewController(forKey: .from) as? ZLPhotoPreviewController, let toVC = context.viewController(forKey: .to) as? ZLThumbnailViewController else {
- return
- }
-
- let fromVCModel = fromVC.arrDataSources[fromVC.currentIndex]
- let toVCVisiableIndexPaths = toVC.collectionView.indexPathsForVisibleItems
-
- var diff = 0
- if toVC.showCameraCell, !ZLPhotoConfiguration.default().sortAscending {
- diff = -1
- }
- var toIndex: Int? = nil
- for indexPath in toVCVisiableIndexPaths {
- let idx = indexPath.row + diff
- if idx >= toVC.arrDataSources.count || idx < 0 {
- continue
- }
- let m = toVC.arrDataSources[idx]
- if m == fromVCModel {
- toIndex = indexPath.row
- break
- }
- }
-
- var toFrame: CGRect? = nil
-
- if let toIdx = toIndex, let toCell = toVC.collectionView.cellForItem(at: IndexPath(row: toIdx, section: 0)) {
- toFrame = toVC.collectionView.convert(toCell.frame, to: context.containerView)
- }
-
- UIView.animate(withDuration: 0.25, animations: {
- if let to = toFrame {
- self.imageView?.frame = to
- } else {
- self.imageView?.alpha = 0
- }
- self.shadowView?.alpha = 0
- }) { (_) in
- self.imageView?.removeFromSuperview()
- self.shadowView?.removeFromSuperview()
- self.imageView = nil
- self.shadowView = nil
- self.finishTransition?()
- context.completeTransition(!context.transitionWasCancelled)
- }
- }
-
- func cancelAnimate() {
- guard let context = self.transitionContext else {
- return
- }
-
- UIView.animate(withDuration: 0.25, animations: {
- self.imageView?.frame = self.imageViewOriginalFrame
- self.shadowView?.alpha = 1
- }) { (_) in
- self.imageView?.removeFromSuperview()
- self.shadowView?.removeFromSuperview()
- self.cancelTransition?()
- context.completeTransition(!context.transitionWasCancelled)
- }
- }
-
- }
|