IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Swift——自定义转场动画(一) -> 正文阅读

[移动开发]Swift——自定义转场动画(一)

弹窗转场/过度动画(Popover效果)

避免浪费大家时间,快速查看运行效果可以直接拉到最后看 【五.完整代码】 部分,如果要看递推逻辑,可以从前往后看。

一.基本设置

弹出一个控制器:系统提供了以下的方法

 @available(iOS 5.0, *)
    open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil)

但是如果想自定义弹出控制器的大小及动画的展示形式,就需要自定义转场
首先UIModalPresentationStyle定义了控制器弹出的方式

@available(iOS 3.2, *)
    open var modalPresentationStyle: UIModalPresentationStyle

要设置为**.custom**,这样弹出控制器后,背后的控制器不会消失,如下:

二.调整大小

//popoverVc是将要弹出来的控制器
popoverVc.modalPresentationStyle = .custom

其次要使控制器遵守转场动画的协议:UIViewControllerTransitioningDelegate

    @available(iOS 2.0, *)
    optional func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?

    
    @available(iOS 2.0, *)
    optional func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

    
    optional func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

    
    optional func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

    
    @available(iOS 8.0, *)
    optional func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?

协议中先看下第五个方法,此方法中,我们可以拿到要弹出的控制器被弹出的控制器,返回值是UIPresentationController
可以认为,系统就是通过这个类型的控制器将我们要弹出的控制器,弹出来的。所以我们自定义一个控制器,继承自UIPresentationController,然后就改变一些东西,比如弹出控制器view的大小:

    open var presentingViewController: UIViewController { get }

    open var presentedViewController: UIViewController { get }
    
    // The view in which a presentation occurs. It is an ancestor of both the presenting and presented view controller's views.
    // This view is being passed to the animation controller.
    open var containerView: UIView? { get }
        // A view that's going to be animated during the presentation. Must be an ancestor of a presented view controller's view
    // or a presented view controller's view itself.
    // (Default: presented view controller's view)
    open var presentedView: UIView? { get }

可以看到,我们可以拿到要弹出来的控制器presentedViewController和它的view presentedView,那么我们改变presentedViewframe,就可以调整弹出控制器的位置大小了。自定义示例如下:

import UIKit

class WXPresentationController: UIPresentationController {
    
    //提供属性,frame由外界传入
    var presentedFrame:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
    
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        presentedView!.frame = presentedFrame
        setupCoverView()
    }
}

/MARK:- 添加蒙版
extension WXPresentationController{
    private func setupCoverView(){
        let coverView = UIView(frame: containerView!.bounds)
        coverView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickCoverView))
        coverView.addGestureRecognizer(tapGesture)
        containerView!.insertSubview(coverView, at: 0)
    }
}

/MARK:- 点击蒙版,view消失
extension WXPresentationController{
    @objc func clickCoverView(){
        presentedViewController.dismiss(animated: true, completion: nil)
    }
}
extension HomeViewController:UIViewControllerTransitioningDelegate{
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let presentationController = WXPresentationController(presentedViewController: presented, presenting: presenting)
        presentationController.presentedFrame = presentedFrame
        return presentationController
    }
}

截止到此处位置,动画依然用的是系统的动画,frame是我们来定义的

三.调整动画

此时再回头看UIViewControllerTransitioningDelegate 中的前两个方法

    @available(iOS 2.0, *)
    optional func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?

    
    @available(iOS 2.0, *)
    optional func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

见名知意,这两个方法,提供两个控制器,forPresentedforDismissed,也就是执行弹出动画的控制器和执行消失动画的控制器,看返回值:UIViewControllerAnimatedTransitioning?
这是个什么类型呢?进去看下:

public protocol UIViewControllerAnimatedTransitioning : NSObjectProtocol {

    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)

很明显,这里是一个协议,那么返回值是一个协议是是什么意思呢?是指的返回一个遵守了这个协议的对象即可。
所以,可以直接返回当前控制器,也就是self,然后在当前控制器中,实现这个协议的两个方法即可。

extension HomeViewController:UIViewControllerAnimatedTransitioning{
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {

    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        
    }
 }

但是弹出动画和消失动画走的是同样的两个代理方法,所以在此通过定义 isPresented变量来加以区分

extension HomeViewController:UIViewControllerTransitioningDelegate{

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = true
        return self
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = false
        return self
    }
}
extension HomeViewController:UIViewControllerAnimatedTransitioning{
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //动画时长
        0.5
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        isPresented ? presentedTransitioin(using: transitionContext) : dismissedTransitioin(using: transitionContext)
        
    }
    
    //封装弹出动画
    func presentedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
        presentedView.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
        transitionContext.containerView.addSubview(presentedView)
        presentedView.transform = CGAffineTransform.init(scaleX: 1, y: 0.0001)
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
            presentedView.transform = CGAffineTransform.identity
        } completion: { _ in
            transitionContext.completeTransition(true)
        }
    }
    
    //封装消失动画
    func dismissedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        let dismissedView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
        	//y:设置为0.00001,不要直接设置为0,防止突变
            dismissedView.transform = CGAffineTransform.init(scaleX: 1.0, y: 0.0001)
        } completion: { _ in
            dismissedView.removeFromSuperview()
            transitionContext.completeTransition(true)
        }
    }
}

四.优化工作

代理方法及动画逻辑都在HomeViewController中,耦合性太强,可以自定义一个PopoverAnimator,继承自NSObject,然后实现相应的代理逻辑,这样就可以将代理逻辑抽取出来加以复用,代理就成了PopoverAnimator的一个实例对象。

extension HomeViewController{
    @objc func titleBtnClick(sender:TitleBtn){
        let popoverVc = PopoverViewController()
        popoverVc.modalPresentationStyle = .custom
        //设置代理
        popoverVc.transitioningDelegate = popoverAnimator
        popoverAnimator.presentedFrame = CGRect(x: view.frame.width/2 - 75, y: 100, width: 150, height: 250)

        present(popoverVc, animated: true, completion: nil)
        
    }
}

至此:我们定义了一个WXPresentationController 来调整弹出控制器的view的层级,加蒙版等,以及一个PopoverAnimator 来调整弹出控制的大小及动画的类。

五.完整代码

import UIKit

class WXPresentationController: UIPresentationController {
    
    //提供属性,frame由外界传入
    var presentedFrame:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
    
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        presentedView!.frame = presentedFrame
        setupCoverView()
    }
}

/MARK:- 添加蒙版
extension WXPresentationController{
    private func setupCoverView(){
        let coverView = UIView(frame: containerView!.bounds)
        coverView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickCoverView))
        coverView.addGestureRecognizer(tapGesture)

        containerView!.insertSubview(coverView, at: 0)
    }
}

/MARK:- 点击蒙版,view消失
extension WXPresentationController{
    @objc func clickCoverView(){
        presentedViewController.dismiss(animated: true, completion: nil)
    }

}

//
//  PopoverAnimator.swift
//  PopoverViewController
//
//  Created by xiao on 2021/11/25.
//

import UIKit

class PopoverAnimator: NSObject {
    var presentedFrame:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
    var isPresented:Bool = false
    var presentedCallBack:((_ isPresented:Bool)->())?

}

//自定义转场动画遵守的协议
//改变弹出控制器的大小
extension PopoverAnimator:UIViewControllerTransitioningDelegate{
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let presentationController = WXPresentationController(presentedViewController: presented, presenting: presenting)
        presentationController.presentedFrame = presentedFrame
        return presentationController
    }
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = true
        //提供回调状态,通知外界
        if let callBack = presentedCallBack{
            callBack(isPresented)
        }
        return self
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = false
        //提供回调状态,通知外界
        if let callBack = presentedCallBack{
            callBack(isPresented)
        }
        return self
    }
}

extension PopoverAnimator:UIViewControllerAnimatedTransitioning{
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //动画时长
        0.5
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        isPresented ? presentedTransitioin(using: transitionContext) : dismissedTransitioin(using: transitionContext)
        
    }
    
    //封装弹出动画
    func presentedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        //通过UITransitionContextViewKey获取做动画的view
        let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
        presentedView.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
        transitionContext.containerView.addSubview(presentedView)
        presentedView.transform = CGAffineTransform.init(scaleX: 1, y: 0.0001)
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
            presentedView.transform = CGAffineTransform.identity
        } completion: { _ in
            transitionContext.completeTransition(true)
        }
    }
    
    //封装消失动画
    func dismissedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        let dismissedView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
            dismissedView.transform = CGAffineTransform.init(scaleX: 1.0, y: 0.0001)
        } completion: { _ in
            dismissedView.removeFromSuperview()
            transitionContext.completeTransition(true)
        }
    }
   
}

调用

import UIKit

class PopoverViewController: UIViewController {
    
    lazy var tableView:UITableView = {
        let tableview = UITableView(frame: CGRect(x: 0, y: 0, width: 150, height: 250), style: .plain)
        return tableview
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
}

///UI
extension PopoverViewController{
    private func setupUI(){
        view.addSubview(tableView)
    }
}
extension HomeViewController{
    @objc func titleBtnClick(sender:TitleBtn){
        let popoverVc = PopoverViewController()
        popoverVc.modalPresentationStyle = .custom
        popoverVc.transitioningDelegate = popoverAnimator
        popoverAnimator.presentedFrame = CGRect(x: view.frame.width/2 - 75, y: 100, width: 150, height: 250)
        popoverAnimator.presentedCallBack = { [weak self]isPresented in
            self?.titleBtn.isSelected = isPresented
        }
        present(popoverVc, animated: true, completion: nil)
        
    }

over

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-30 15:43:53  更:2021-11-30 15:44:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:20:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码