文章目录
前言
此次学习的是View Controller Transitions中的自定义Modally转场动画中的非交互动画
一、设置页面MianViewController和DetailViewController
二、点击按钮跳转页面
1.在MainViewController中使用showDetail来present出DetailView
@IBAction func showDetail(_ sender: Any) {
let detailVC = storyboard!.instantiateViewController(withIdentifier: "DetailVC") as! DetailViewController
detailVC.modalPresentationStyle = .fullScreen
present(detailVC, animated: true, completion: nil)
}
在present中获取UIViewController,既Storyboard中已设计的DetailViewController,不能直接实例化,否则点击按钮跳转页面为空。需在Storyboard的DetailViewController下找到identify中的storyboard ID,赋值为DetailVC,再在showDetail中初始ViewController
let detailVC = storyboard!.instantiateViewController(withIdentifier: "DetailVC") as! DetailViewController
iOS13版本中modally方式弹出的页面默认变为非全屏状态,所以需在prsent前添加:detailVC.modalPresentationStyle = .fullScreen
2.使用Modally方式展示DetailVC
2.1、detailVC的present和dismiss委托给当前View Controller处理
在present之前指定代理人,让代理人return自定义动画器,系统找到非nil的动画器后执行自定义动画,否则继续执行系统动画
detailVC.transitioningDelegate = self
extension MainViewController: UIViewControllerTransitioningDelegate{
//非交互动画--视觉动画
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//创建PresentAnimator动画实例,遵循UIViewControllerAnimatedTransitioning
return PresentAnimator()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DismissAnimator()
}
}
2.2、创建PresentAnimator动画实例
PresentAnimator类要遵循NSObject协议,才可以继承UIViewControllerAnimatedTransitioning
class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//因整个动画过程千变万化,一定要直接使用transitionContext中的数据
//不要用变量来缓存数据然后再用,或比如:获取fromView时用fromVC.view
//let fromVC = transitionContext.viewController(forKey: .from)
//let toVC = transitionContext.viewController(forKey: .to)
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else{return}
let containerView = transitionContext.containerView
//containerview会自动把fromview加到视图层级中,我们需要自己加上toview--两个view到齐才能开始动画
//containerview在动画结束后会自动移除fromview防止资源浪费,不需要我们手动移除
containerView.addSubview(toView)
//动画起始状态
toView.alpha = 0
toView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
//动画结束状态
fromView.alpha = 0
fromView.transform = CGAffineTransform(translationX: -containerView.frame.width, y: 0)
toView.alpha = 1
toView.transform = .identity
}) { _ in
//fromview虽然被自动从containerview里面移除了,但附在身上的transform属性还在,为了防止两次动画交错,这里要置空
fromView.transform = .identity
//虽然toview的transform在动画块里面置空了,但如果交互动画时用户取消转场了,就会被附上初始时定的transform,所以为了不干扰下一次动画,这里也置空
toView.transform = .identity
//transitionWasCancelled-如果动画被取消了,必须撤销之前对视图层级所做的修改
// !transitionContext.transitionWasCancelled
//一定要调用completeTransition,不然页面会卡住
//调用后UIKit才会结束整个动画流程,并把控制权交给App,用户才可以继续操作页面
transitionContext.completeTransition(true)
}
}
}
三、自定义dismiss转场动画
1.imageView添加tap手势
@IBOutlet weak var detailImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(tap:)))
detailImageView.addGestureRecognizer(tap)
}
@objc func handleTap(tap:UITapGestureRecognizer){
dismiss(animated: true,completion: nil)
}
imageView不是UIViewController类型,在默认情况下,是不允许用户进行交互操作的,为了防止与其他的可交互的按键起冲突。故给其添加手势要在Interaction中将可交互勾上。
2.创建DissmissAnimator动画实例
class DismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else{return}
containerView.addSubview(toView)
toView.alpha = 0
toView.transform = CGAffineTransform(translationX: -containerView.frame.width, y: 0)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fromView.alpha = 0
fromView.transform = CGAffineTransform(translationX: containerView.frame.width, y: 0)
toView.alpha = 1
toView.transform = .identity
}) { _ in
//一旦给fromView和toView修改了它们transform属性,都需在完成后的回调函数中置空
//fromview虽然被自动从containerview里面移除了,但附在身上的transform属性还在,为了防止两次动画交错,这里要置空
fromView.transform = .identity
//虽然toview的transform在动画块里面置空了,但如果交互动画时用户取消转场了,就会被附上初始时定的transform,所以为了不干扰下一次动画,这里也置空
toView.transform = .identity
transitionContext.completeTransition(true)
}
}
}
再将DismissAnimator在forDismissed dismissed函数中return
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DismissAnimator()
}
完成后整体的效果如下所示
总结
此次是对ViewController Transition中非交互动画Modally的学习,主要了解到自定义的present和dismiss,以及imageView添加手势动画要将其交互设计为true。fromView、toView的展示切换中,一旦给fromView和toView修改了它们transform属性,都需在完成后的回调函数中置空。总体上加深了对自定义非交互式动画的的了解,接下来学习交互式动画打下基础。