效果:
假設(shè)有:
1.兩個(gè)視圖控制器:presentingVC, presentedVC
2.一個(gè)繼承于UIPercentDrivenInteractiveTransition
,并遵守協(xié)議UIViewControllerAnimatedTransitioning
的實(shí)例:transitionAnimator
3.presentingVC.present(presentedVC, animated: true, completion: nil)
一. 自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- 要自定義dismiss轉(zhuǎn)場(chǎng)動(dòng)畫(huà),presentedVC視圖控制器就要遵守
UIViewControllerTransitioningDelegate
協(xié)議(不要忘記設(shè)置presentedVC.transitioningDelegate = self
)纫骑,并且實(shí)現(xiàn)以下兩個(gè)方法:
1) 提供present動(dòng)畫(huà)
//這個(gè)方法在調(diào)用presentingVC.present(presentedVC, animated: true, completion: nil)時(shí)送矩,被調(diào)用
// 返回transitionAnimator赶撰,正是由這個(gè)實(shí)例提供自定義轉(zhuǎn)場(chǎng)的
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?{
return transitionAnimator
}
2) 提供dismiss動(dòng)畫(huà)
// 這個(gè)方法在調(diào)用presentedVC.dismiss(animated: true, completion: nil)時(shí),被調(diào)用
// 返回實(shí)例 transitionAnimator
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?{
return transitionAnimator
}
- 類(lèi)
TransitionAnimator
遵守UIViewControllerAnimatedTransitioning
協(xié)議糊啡,并必須實(shí)現(xiàn)以下兩個(gè)協(xié)議方法:
//返回轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的持續(xù)時(shí)間
1) func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
//實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
2) func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
二. 滑動(dòng)手勢(shì)控制 dismiss 過(guò)程
- 根據(jù)滑動(dòng)手勢(shì)的偏移量來(lái)設(shè)置dismiss動(dòng)畫(huà)的完成百分比拄查,再讓presentedVC視圖控制器實(shí)現(xiàn)
UIViewControllerTransitioningDelegate
協(xié)議的另一個(gè)方法:
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning)
-> UIViewControllerInteractiveTransitioning?{
return transitionAnimator
}
- 根據(jù)滑動(dòng)手勢(shì)的偏移量,設(shè)置dismiss 動(dòng)畫(huà)的完成百分比
transitionAnimator.transitionAnimator.update(precent)
二. 關(guān)鍵代碼
presentedVC
import UIKit
protocol DetialViewControllerDelegate {
func detialViewController(_ controller: DetialViewController, scrollViewPanGestureRecognizerDidChange:UIPanGestureRecognizer)
}
struct PanningData {
let contentOffset: CGPoint
let isDragging: Bool
let translation: CGPoint
let velocity: CGPoint
}
/// 轉(zhuǎn)場(chǎng)狀態(tài)
enum TransitionState: String{
case none
case started
case updating
case canceling
case finishing
fileprivate var active: Bool {
return self == .started || self == .updating
}
}
class DetialViewController: UITableViewController{
var delegate: DetialViewControllerDelegate?
var transitionAnimator = TransitionAnimator()
var transitionPhase : TransitionState = .none
override func viewDidLoad() {
super.viewDidLoad()
self.transitioningDelegate = self
tableView.delegate = self
tableView.dataSource = self
// tableView.bounces = false
tableView.panGestureRecognizer.addTarget(self, action: #selector(detailViewController(_:)))
}
}
extension DetialViewController:UIViewControllerTransitioningDelegate{
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "sss")
if (cell == nil) {
cell = UITableViewCell(style: .default, reuseIdentifier: "sss")
}
cell?.textLabel?.text = String(indexPath.row)
return cell!
}
@objc func detailViewController(_ recognizer: UIPanGestureRecognizer){
guard let scrollView = recognizer.view as? UIScrollView else { return }
let data = PanningData(contentOffset: scrollView.contentOffset, isDragging: scrollView.isTracking, translation: recognizer.translation(in: scrollView), velocity: recognizer.velocity(in: scrollView))
if data.contentOffset.y > 0 {
transitionPhase = transitionPhase == .none ? .none : .canceling
}else if data.isDragging && data.translation.y > 0 && !transitionPhase.active {
transitionPhase = .started
}
else if data.isDragging && data.translation.y > 0 && transitionPhase.active {
transitionPhase = .updating
}
else if data.isDragging && data.translation.y < 0 && transitionPhase.active {
transitionPhase = .canceling
}
else if !data.isDragging && data.translation.y > 0 && transitionPhase.active {
transitionPhase = data.velocity.y > 0 ? .finishing : .canceling
}
if transitionPhase == .started || transitionPhase == .updating{
self.transitionAnimator.isInFlight = true
}else {
self.transitionAnimator.isInFlight = false
}
print(transitionPhase.rawValue)
if transitionPhase == .started {
scrollView.bounces = false
self.dismiss(animated: true, completion: nil)
}
if transitionPhase == .updating || transitionPhase == .started {
let precent = data.translation.y / self.view.bounds.height
self.transitionAnimator.update(precent)
}
if transitionPhase == .canceling {
scrollView.bounces = true
self.transitionAnimator.cancel()
}
if transitionPhase == .finishing {
self.transitionAnimator.finish()
}
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self.transitionAnimator
}
func animationController(forPresented presented: UIViewController,presenting: UIViewController,source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self.transitionAnimator
}
///這個(gè)協(xié)議用來(lái)返回控制 dismiss 完成度的類(lèi)UIPercentDrivenInteractiveTransition,此類(lèi)遵守 UIViewControllerInteractiveTransitioning
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning)
-> UIViewControllerInteractiveTransitioning? {
return self.transitionAnimator.isInFlight ? self.transitionAnimator : nil
}
}
TransitionAnimator
import UIKit
class TransitionAnimator: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning{
private let dismissedOverlayColor = UIColor(white: 1, alpha: 0)
private let presentedOverlayColor = UIColor(white: 1, alpha: 1)
fileprivate let darkOverlayView = UIView()
internal var isInFlight = true
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return transitionContext?.isInteractive == .some(true) ? 0.6:0.4
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
else{ return }
let containerView = transitionContext.containerView
if toVC.isBeingPresented {
self.animatePresentation(fromViewController: fromVC,
toViewController: toVC,
containerView: containerView,
transitionContext: transitionContext)
}else{
self.animateDismissal(fromViewController: fromVC,
toViewController: toVC,
containerView: containerView,
transitionContext: transitionContext)
}
}
fileprivate func animatePresentation(
fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController,
containerView: UIView,
transitionContext: UIViewControllerContextTransitioning) {
containerView.insertSubview(toVC.view, aboveSubview: fromVC.view)
containerView.insertSubview(self.darkOverlayView, belowSubview: toVC.view)
self.darkOverlayView.frame = containerView.bounds
self.darkOverlayView.backgroundColor = dismissedOverlayColor
let bottomLeftCorner = CGPoint(x: 0, y: containerView.bounds.height)
let finalFrame = CGRect(origin: .zero, size: containerView.bounds.size)
toVC.view.frame = CGRect(origin: bottomLeftCorner, size: containerView.bounds.size)
UIView.animate(
withDuration: self.transitionDuration(using: transitionContext),
delay: 0,
options: [.curveEaseOut],
animations: {
toVC.view.frame = finalFrame
self.darkOverlayView.backgroundColor = self.presentedOverlayColor
},
completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
)
}
/// toViewController 表示將要出現(xiàn)的VC
/// fromViewController 表示將要dismiss的VC
fileprivate func animateDismissal(fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController,
containerView: UIView,
transitionContext: UIViewControllerContextTransitioning) {
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
containerView.insertSubview(self.darkOverlayView, belowSubview: fromVC.view)
self.darkOverlayView.frame = containerView.bounds
self.darkOverlayView.backgroundColor = presentedOverlayColor
let bottomLeftCorner = CGPoint(x: 0, y: containerView.bounds.height)
let finalFrame = CGRect(origin: bottomLeftCorner, size: containerView.bounds.size)
toVC.view.frame = containerView.bounds
let animationCurve: UIViewAnimationOptions = transitionContext.isInteractive == .some(true)
? .curveLinear
: .curveEaseOut
UIView.animate(
withDuration: self.transitionDuration(using: transitionContext),
delay: 0,
options: [animationCurve],
animations: {
fromVC.view.frame = finalFrame
if transitionContext.isInteractive {
fromVC.view.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}
self.darkOverlayView.backgroundColor = self.dismissedOverlayColor
},
completion: { _ in
self.darkOverlayView.backgroundColor = transitionContext.transitionWasCancelled
? .black
: self.darkOverlayView.backgroundColor
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
)
}
}