自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫(huà)游盲,滑動(dòng)手勢(shì)控制 dismiss 過(guò)程

效果:


假設(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à)

  1. 要自定義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
}
  1. 類(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ò)程

  1. 根據(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
}
  1. 根據(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)
        }
        )
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棚蓄,一起剝皮案震驚了整個(gè)濱河市堕扶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梭依,老刑警劉巖稍算,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異役拴,居然都是意外死亡糊探,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)科平,“玉大人褥紫,你說(shuō)我怎么就攤上這事〉苫郏” “怎么了故源?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)汞贸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)印机,這世上最難降的妖魔是什么矢腻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮射赛,結(jié)果婚禮上多柑,老公的妹妹穿的比我還像新娘。我一直安慰自己楣责,他們只是感情好竣灌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著秆麸,像睡著了一般初嘹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沮趣,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天屯烦,我揣著相機(jī)與錄音,去河邊找鬼房铭。 笑死驻龟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缸匪。 我是一名探鬼主播翁狐,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼凌蔬!你這毒婦竟也來(lái)了露懒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤砂心,失蹤者是張志新(化名)和其女友劉穎隐锭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體计贰,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钦睡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躁倒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荞怒。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洒琢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出褐桌,到底是詐尸還是另有隱情衰抑,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布荧嵌,位于F島的核電站呛踊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啦撮。R本人自食惡果不足惜谭网,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赃春。 院中可真熱鬧愉择,春花似錦、人聲如沸织中。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狭吼。三九已至层坠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刁笙,已是汗流浹背窿春。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留采盒,地道東北人旧乞。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像磅氨,于是被迫代替她去往敵國(guó)和親尺栖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容