自定义modal转场动画,滑动手势控制 dismiss 过程

2023-11-20

效果:

假设有:
1.两个视图控制器:presentingVC, presentedVC
2.一个继承于UIPercentDrivenInteractiveTransition,并遵守协议UIViewControllerAnimatedTransitioning的实例:transitionAnimator
3.presentingVC.present(presentedVC, animated: true, completion: nil)

一. 自定义modal转场动画
  1. 要自定义dismiss转场动画,presentedVC视图控制器就要遵守UIViewControllerTransitioningDelegate协议(不要忘记设置 presentedVC.transitioningDelegate = self),并且实现以下两个方法:
1) 提供present动画
//这个方法在调用presentingVC.present(presentedVC, animated: true, completion: nil)时,被调用
// 返回transitionAnimator,正是由这个实例提供自定义转场的
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?{
  return transitionAnimator
}

2) 提供dismiss动画
// 这个方法在调用presentedVC.dismiss(animated: true, completion: nil)时,被调用
// 返回实例 transitionAnimator
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?{
  return transitionAnimator
}
  1. TransitionAnimator遵守UIViewControllerAnimatedTransitioning协议,并必须实现以下两个协议方法:
 //返回转场动画的持续时间
 1) func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval 

//实现转场动画
 2) func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
二. 滑动手势控制 dismiss 过程

1) 根据滑动手势的偏移量来设置dismiss动画的完成百分比,再让presentedVC视图控制器实现UIViewControllerTransitioningDelegate协议的另一个方法:


func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning)
        -> UIViewControllerInteractiveTransitioning?{
  return transitionAnimator
}
  1. 根据滑动手势的偏移量,设置dismiss 动画的完成百分比
   transitionAnimator.transitionAnimator.update(precent)
二. 关键代码

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
}
/// 转场状态
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
    }

    ///这个协议用来返回控制 dismiss 完成度的类UIPercentDrivenInteractiveTransition,此类遵守 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 表示将要出现的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)
        }
        )
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自定义modal转场动画,滑动手势控制 dismiss 过程 的相关文章

  • 如何使用 SwiftUI 获取多个屏幕上的键盘高度并移动按钮

    以下代码获取键盘显示时的键盘高度 并将按钮移动键盘高度 在转换源 ContentView 和转换目标 SecibdContentView 处以相同的方式执行此移动 但按钮在转换目标处不移动 如何使按钮在多个屏幕上移动相同 import Sw
  • 保存来自 TrueDepth 相机的深度图像

    我正在尝试保存 iPhone X TrueDepth 相机的深度图像 使用AVCam照片滤镜 https developer apple com library content samplecode AVCamPhotoFilter Lis
  • Swift:检查 UISearchBar.text 是否包含 url

    如何检查 UISearchBar text 是否包含 URL 我想做这样的事情 if searchBar text NSTextCheckingType Link 但我收到错误 String is not convertible to NS
  • 如何将CIFilter应用到UIView上?

    根据Apple docs 过滤属性CALayer不支持iOS 当我使用正在申请的应用程序之一时CIFilter to UIView即 Splice Funimate 和 Artisto 的视频编辑器 Videoshow FX 这意味着我们可
  • WKWebview 中的 iCLoud 文档选择器关闭容器视图

    我有一个 WKWebview 加载基于 Web 的 UI 我希望用户能够从其 iCloud 文档上传文件 我已授予正确的权限 并且可以浏览 iCloud 文档 但是 当我选择文件或单击取消按钮时 文档选择器视图也会关闭 WKWebview
  • 具有动态警报正文的快速本地通知

    所以我可以创建一个像这样的本地通知 var localNotification UILocalNotification localNotification fireDate NSDate timeIntervalSinceNow 7 loc
  • Xcode 错误 - 架构 x86_64 的未定义符号?

    我正在运行 Swift 4 和 Xcode 9 beta 我收到此错误 但我不知道如何解决它 我什至不知道这是什么意思 Undefined symbols for architecture x86 64 T0So22AVCapturePho
  • 删除派生数据文件夹后,Xcode 不断重新创建派生数据文件夹

    自动完成功能在 Xcode 6 中不再起作用 我四处搜索 发现删除派生数据文件夹可以解决此问题 每次我删除它时 它都会回来 然后就不会再自动完成了 有什么建议么 Thanks 没关系 我解决了这个问题 我没有声明需要在类内的方法中使用的变量
  • 如何右对齐 UILabel?

    Remark 实施 myLabel textAlignment right does not解决了我的问题 这不是我所要求的 我想要实现的是让标签对齐右对齐 为了更清楚地说明 这就是如何left对齐外观 就是这样justify对齐外观 if
  • Swift 中的柯里函数

    我想创建一个返回柯里函数的函数 如下所示 func addTwoNumbers a Int b Int gt Int return a b addTwoNumbers 4 b 6 Result 10 var add4 addTwoNumbe
  • Swift:如何减少 didupdatelocations 调用

    我想出了一些代码来打印我所在位置的地址和邮政编码 这是在 didupdatelocation 函数中完成的 我遇到的唯一问题是 didupdatelocation 函数每秒都会更新该地址 因为这电池效率非常低 所以我一直在寻找使用间隔的方法
  • 用于字数计算的 Swift String 中的字数

    我想做一个程序来找出字符串中有多少个单词 用空格 逗号或其他字符分隔 然后把总数加起来 我正在制作一个平均计算器 所以我想要数据总数 然后将所有单词相加 update Xcode 10 2 x Swift 5 或更高版本 使用基础方法enu
  • 如何在 Swift 中获取字典中最后输入的值?

    如何获取 Swift 字典中最后输入的值 例如 我如何从下面获取值 CCC var dictionary Dictionary
  • 致命错误:在字典中发现“地理编码地标”类型的重复键。 (Mapbox 地理编码器)

    我引用 这通常意味着要么该类型违反了 Hashable 的要求 要么此类字典的成员在插入后发生了变化 我正在使用 Mapbox Geocoder 当发生此运行时错误时 我的 XCode 将我带到线程 1 0 swift runtime on
  • SKNode 上的 runAction 未完成

    我使用 NSOperation 子类来获取串行执行SKAction正如这个问题中所描述的 如何在 Swift 中子类化 NSOperation 以将 SKAction 对象排队以进行串行执行 https stackoverflow com
  • init 中的 Swift 通用约束

    我有通用的 我希望能够用特定的约束来初始化它 约束仅用于初始化 班里的其他人并不关心 这是一个简化的示例 struct Generic
  • Swift 中的 UIAlert 自动消失?

    我有以下代码 Creates Alerts on screen for user func notifyUser title String message String gt Void let alert UIAlertController
  • IPV6 快速可达性

    我是 swift 和 xcode 的新手 并且我的应用程序因 IPV6 而被拒绝 性能 2 1 当我们执行以下操作时 您的应用程序会在运行 iOS 9 3 5 并连接到 IPv6 网络的 iPad 和 iPhone 上崩溃 具体来说 当我们
  • iOS 13 beta 外部屏幕上的 OverscanCompensation

    我正在测试一个应用程序的测试版 但遇到了外部屏幕的问题 我们看到应用程序周围有黑色边框 我们之前可以通过设置来纠正它overscanCompensation to none但在 iOS 13 中 该设置根本没有任何效果 我们曾经看到一个错误
  • 在 iOS 11 中创建 Gif 图像颜色贴图

    最近 我在创建 Gif 时遇到了一个问题 如果它太大 颜色就会丢失 然而 感谢 SO 的帮助 有人能够帮助我找到解决方法并创建我自己的颜色图 上一个问题在这里 保存动画 Gif 时 iOS 颜色不正确 https stackoverflow

随机推荐

  • JavaScript 节流和防抖

    前言 本文主要记录了JavaScript 节流和防抖 节流和防抖本质上是优化执行高频率代码的一种手段 例如 浏览器的 mousemove resize scroll 等事件在触发时 会不断地调用绑定的事件函数极大地降低了前端的性能 为了性能
  • C#、js如何实现文件上传功能

    上传文件 今天我来讲讲在MVC中如何进行文件的上传 我们逐步深入 一起来看看 我们在默认创建的项目中的控制器下添加如下 第一步创建一个接受文件的实体 创建好后判断一下接受文件的是什么文件类型如txt 然后就是文件名称建好后检查目录文件是否存
  • CVPR2017-如何在无标签数据集上训练模型

    论文 Fine tuning Convolutional Neural Networks for Biomedical Image Analysis Actively and Incrementally 论文链接 http openacce
  • 深入解析Spring Boot中最常用注解的使用方式(下篇)

    摘要 本文是 深入解析Spring Boot中最常用注解的使用方式 的下篇内容 将继续介绍Spring Boot中其他常用的注解的使用方式 并通过代码示例进行说明 帮助读者更好地理解和运用Spring Boot框架 目录 第二部分 常见的容
  • 全国职业技能大赛云计算--高职组赛题卷④(容器云)

    全国职业技能大赛云计算 高职组赛题卷 容器云 第二场次题目 容器云平台部署与运维 任务1 Docker CE及私有仓库安装任务 5分 任务2 基于容器的web应用系统部署任务 15分 任务3 基于容器的持续集成部署任务 15分 任务4 Ku
  • 哈希表的设计

    概念 顺序结构以及平衡树 中 元素关键码与其存储位置之间没有对应的关系 因此在 查找一个元素时 必须要经过关键 码的多次比较 顺序查找时间复杂度为 O N 平衡树中为树的高度 即 O 搜索的效率取决于搜索过程中 元素的比较次数 理想的搜索方
  • 主页自定义可拖动组件 2.0版本 (portlet)

    首先 我是从下面这个页面抠出来的 http wrapbootstrap com preview WB00958H8 效果 在这个页面直接右键查看源代码 就可以看到了 非常清楚 因为我就只用这么一个portlet功能 我就给抠下来了 原来的代
  • webservice ajax课程,net webservice ajax访问

    function getdata click function ajax type Post url http localhost 65497 WebSite1 WebService asmx GetAge id 3344 data id
  • onload和ready的区别

    三个方面 window onload document ready 执行时机 必须等待网页全部加载完毕 包括图片等 然后再执行包裹代码 只需要等待网页中DOM结构加载完毕 就能执行包裹的代码 执行次数 只能执行一次 如果第二次 那么第一次的
  • 性能优化——设计更优的分布式锁?

    那什么是分布式锁呢 它又是用来解决哪些问题的呢 在 JVM 中 在多线程并发的情况下 我们可以使用同步锁或 Lock 锁 保证在同一时间内 只能有一个线程修改共享变量或执行代码块 但现在我们的服务基本都是基于分布式集群来实现部署的 对于一些
  • 主力吸筹猛攻指标源码_通达信指标——一目了然,简单易懂的变色成交量副图指标...

    大家好 这期给大家分享的变色成交量副图指标看量柱颜色变化 一目了然 换手率 VOL CAPITAL 100 主力吸筹 换手率 gt 3 AND 换手率 lt 5 COLORFF00FF 加快吸筹 换手率 gt 5 AND 换手率 lt 7
  • 智能指针之与shared_ptr有关的enable_shared_from_this类模板04

    一 enable shared from this模板类详解 什么时候该使用enable shared from this模板类 当我们需要一个类对象返回本身并且该类使用了shared ptr智能指针时 就需要使用enable shared
  • Ubuntu20中,rosdep init失败,无法rosdep update问题的解决。百分百有效。

    一 进入到 sources list d 文件夹 修改 gedit 20 default list cd etc ros rosdep sources list d sudo gedit 20 default list 将里面所有的 raw
  • gdb attach 进程调试

    gdb调试正在运行的进程 GDB可以对正在执行的程序进行调度 它允许开发人员中断程序 并查看其状态 之后还能让这个程序正常地继续执行 gdb attach xxxxx xxxxx为利用ps命令获得的子进程process
  • interview

    收割机博客 https blog csdn net DERRANTCM article details 73456550 查找算法 https blog csdn net derrantcm article details 51534498
  • sqlite升级

    最近开发中遇到了需要改变项目数据库的表中字段 添加新表等需求 而又需要保证原有数据不变 这就涉及到数据库升级 现在就来总结记录一下 包含原表中增加字段 删除字段 修改字段 添加新表等四种升级操作 SQLiteOpenHelper类中有两个方
  • 华为校招机试题-猜字谜-2023年

    题目描述 小王设计了一个简单的猜字谜游戏 游戏的谜面是一个错误的单词 比如nesw 玩家需要猜出谜底库中正确的单词 猜中的要求如下 对于某个谜面和谜底单词 满足下面任一条件都表示猜中 1 变换顺序以后一样的 比如通过变换w和e的顺序 nwe
  • App隐私监管新规实施 隐私合规检测要注意这几点?

    5月1日 国家四部委联合制定的 常见类型移动互联网应用程序必要个人信息范围规定 简称 规定 将正式实施 规定 明确移动互联网应用程序 App 运营者不得因用户不同意收集非必要个人信息 而拒绝用户使用App基本功能服务 要求各地各相关单位督促
  • Linux创建文件的5种方式

    Linux创建文件的5种方式 1 touch 1 1 创建一个文件 touch yyTest ini 1 2 同时创建两个文件 touch test1 txt test2 txt 1 3 批量创建文件 如创建2000个文件 touch te
  • 自定义modal转场动画,滑动手势控制 dismiss 过程

    效果 假设有 1 两个视图控制器 presentingVC presentedVC 2 一个继承于UIPercentDrivenInteractiveTransition 并遵守协议UIViewControllerAnimatedTrans