簡(jiǎn)介
上一篇文章蝴蜓,我們討論了UIPresentationController鞭缭,實(shí)現(xiàn)了初步的過場(chǎng)動(dòng)畫自定義受神,也就是背景逐漸顯示抛猖,逐漸影藏。還有就是點(diǎn)擊蒙板退出鼻听,內(nèi)容高度自定義(不是全屏)财著。不過用下來還有以下幾個(gè)問題:
(1)加載是,從下到上的彈出很快就結(jié)束了撑碴,而背景蒙板的逐漸顯示時(shí)間較長(zhǎng)撑教。這是因?yàn)閺南碌缴系膹棾鰟?dòng)畫還是系統(tǒng)默認(rèn)的行為,時(shí)間較短醉拓,大約0.3秒左右伟姐。而蒙板的逐漸顯示是自定義的動(dòng)畫行為,我設(shè)置了較長(zhǎng)時(shí)間(3秒)亿卤,想看清楚一點(diǎn)愤兵。
(2)退出時(shí),更加明顯排吴,從上到下的退出很快秆乳,而背景蒙板的逐漸隱藏幾乎看不到。隨著系統(tǒng)默認(rèn)動(dòng)畫較快地退出,containerView都被系統(tǒng)收回了屹堰,其子視圖的動(dòng)畫當(dāng)然消失了肛冶。
實(shí)現(xiàn)自定義動(dòng)畫
現(xiàn)在不想要系統(tǒng)的從下到上的動(dòng)畫,而是實(shí)現(xiàn)從右到左的動(dòng)畫扯键。
統(tǒng)一時(shí)間長(zhǎng)度淑趾,讓視圖的進(jìn)場(chǎng)動(dòng)畫和背景蒙板顯示動(dòng)畫同步。
統(tǒng)一參數(shù)
(1) 動(dòng)畫結(jié)束后忧陪,最終vc的frame需要確定扣泊,這個(gè)當(dāng)做參數(shù)放入公共的代理對(duì)象中(就是那個(gè)default單例)
(2)動(dòng)畫的時(shí)長(zhǎng),幾個(gè)相關(guān)的對(duì)象都要用統(tǒng)一的參數(shù)嘶摊,這個(gè)也放在代理對(duì)象中
class TempTransitionDelegate: NSObject {
/// 默認(rèn)單例
public static let `default`: TempTransitionDelegate = {
LogUtil.doPrint("TempTransitionDelegate `default` 實(shí)例被創(chuàng)建")
return TempTransitionDelegate()
}()
/// 設(shè)置彈窗視圖的高度
public var sheetHeight: CGFloat = 500
let phoneWidth = UIScreen.main.bounds.width
let phoneHeight = UIScreen.main.bounds.height
var contentFrame: CGRect {
CGRect(origin: CGPoint(x: 0, y: (phoneHeight - sheetHeight)), size: CGSize(width: phoneWidth, height: sheetHeight))
}
/// 設(shè)置動(dòng)畫時(shí)間(秒)
public var animateDuration: TimeInterval = 3
}
自定義動(dòng)畫
- 自定義對(duì)話延蟹,需要一個(gè)實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning的對(duì)象
@MainActor public protocol UIViewControllerAnimatedTransitioning : NSObjectProtocol {
// This is used for percent driven interactive transitions, as well as for
// container controllers that have companion animations that might need to
// synchronize with the main animation.
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
// This method can only be a no-op if the transition is interactive and not a percentDriven interactive transition.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
- 這兩個(gè)函數(shù)都涉及到一個(gè)參數(shù)UIViewControllerContextTransitioning,這是動(dòng)畫過程中叶堆,系統(tǒng)給的一些有用信息
@MainActor public protocol UIViewControllerContextTransitioning : NSObjectProtocol {
// The view in which the animated transition should take place.
@available(iOS 2.0, *)
var containerView: UIView { get }
// This must be called whenever a transition completes (or is cancelled.)
// Typically this is called by the object conforming to the
// UIViewControllerAnimatedTransitioning protocol that was vended by the transitioning
// delegate. For purely interactive transitions it should be called by the
// interaction controller. This method effectively updates internal view
// controller state at the end of the transition.
func completeTransition(_ didComplete: Bool)
}
// Currently only two keys are defined by the
// system - UITransitionContextToViewControllerKey, and
// UITransitionContextFromViewControllerKey.
// Animators should not directly manipulate a view controller's views and should
// use viewForKey: to get views instead.
@available(iOS 2.0, *)
func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?
containerView可以和UIPresentationController中的一樣理解阱飘,簡(jiǎn)單講就是一個(gè)全屏的容器。
completeTransition在動(dòng)畫結(jié)束的時(shí)候調(diào)用一下虱颗,告訴系統(tǒng)過場(chǎng)動(dòng)畫結(jié)束了沥匈。
viewController比較特殊,通過from和to兩個(gè)key忘渔,得到不同的VC高帖。進(jìn)場(chǎng)時(shí),我們顯然是需要toVC畦粮;而消失時(shí)散址,我們是需要fromVC。(多么腦殘的人才能想出這種API宣赔?)
所以预麸,除了frame和動(dòng)畫時(shí)長(zhǎng),我們還需要一個(gè)參數(shù)isPresent來區(qū)分是載入還是退出儒将。這兩者的動(dòng)畫方向是不一樣的吏祸。
考慮了以上因素后,代碼大致如下:
class TempAnimate: NSObject {
/// 載入還是退出的標(biāo)志
public var isPresent = true
/// 視圖最終的frame
public var frame: CGRect = UIScreen.main.bounds
/// 動(dòng)畫時(shí)長(zhǎng)
public var duration: TimeInterval = 0.3
}
extension TempAnimate: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
/// 獲取源視圖控制器和目標(biāo)視圖控制器的視圖
let fromVC = transitionContext.viewController(forKey: .from)!
let toVC = transitionContext.viewController(forKey: .to)!
/// 獲取容器視圖钩蚊,即動(dòng)畫發(fā)生的視圖
let containerView = transitionContext.containerView
if isPresent {
/// 進(jìn)場(chǎng)動(dòng)畫從右向左進(jìn)入屏幕贡翘,取toVC
containerView.addSubview(toVC.view)
toVC.view.frame = frame.offsetBy(dx: UIScreen.main.bounds.width, dy: 0)
UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
toVC.view.frame = self.frame
} completion: { _ in
transitionContext.completeTransition(true)
}
} else {
/// 退出動(dòng)畫從右向左離開屏幕,取fromVC
containerView.addSubview(fromVC.view)
fromVC.view.frame = frame
UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
fromVC.view.frame = self.frame.offsetBy(dx: -UIScreen.main.bounds.width, dy: 0)
} completion: { _ in
transitionContext.completeTransition(true)
}
}
}
}
在代理方法中統(tǒng)一傳參
/// 代理方法
extension TempTransitionDelegate: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
LogUtil.doPrint("TempTransitionDelegate `animationController` forPresented 被調(diào)用")
let animate = TempAnimate()
animate.isPresent = true
animate.duration = animateDuration
animate.frame = contentFrame
return animate
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
LogUtil.doPrint("TempTransitionDelegate `animationController` forDismissed 被調(diào)用")
let animate = TempAnimate()
animate.isPresent = false
animate.duration = animateDuration
animate.frame = contentFrame
return animate
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
LogUtil.doPrint("TempTransitionDelegate `interactionControllerForPresentation` 被調(diào)用")
return nil
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
LogUtil.doPrint("TempTransitionDelegate `interactionControllerForDismissal` 被調(diào)用")
return nil
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
presented.modalPresentationStyle = .custom
LogUtil.doPrint("TempTransitionDelegate `presentationController` 被調(diào)用")
let present = TempPresentation(presentedViewController: presented, presenting: presenting)
present.duration = animateDuration
present.frame = contentFrame
return present
}
}
大致的效果
同時(shí)放棄了系統(tǒng)從下到上的默認(rèn)彈出效果两疚,而是實(shí)現(xiàn)了從右到左的彈出效果床估,并且速度慢很多含滴。