前言

对于Apple默认的转场动画(push、pop、modal)我们再熟悉不过了,但是这些显得单调的风格早已无法满足日益挑剔的用户了,对于极其重视用户体验的Apple肯定是不会让这个问题遗留下去的。早在iOS7,Apple就开放了视图控制器转场的API,到如今又迭代了三代iOS,相应的API已经很稳定了。如果是注意App体验的朋友一定发现了目前市面上的App导航控制器的转场绝大多数都是系统的默认实现,即经典的push、pop。它们最大的好处就是简单方便,对于功能性的App其实就已经够了,但是对于娱乐性的了?

娱乐性的App通常都具有良好的UI设计以及酷炫的视觉效果,这两者的合理融合是它们相互竞争的利器,同时也决定了用户的取向。像风靡全球的angry Birds,笔者第一次接触这个游戏是那真是眼睛发亮,根本停不下来啊!所以为了今后的App能脱颖而出,就先定一个小目标——征服自定义过渡!

体会Apple的架构

UIPresentationController

UIPresentationController是iOS8新增加的类,以下是Apple官方文档对它的简介:

A UIPresentationController object provides advanced view and transition management for presented view controllers. From the time a view controller is presented until the time it is dismissed,UIKit uses a presentation controller to manage varIoUs aspects of the presentation process for that view controller. The presentation controller can add its own animations on top of those provided by animator objects,it can respond to size changes,and it can manage other aspects of how the view controller is presented onscreen.

大意为:

UIPresentationController对象对于呈现的视图控制器提供了高级的视图和过渡管理。从一个视图控制器呈现时到退场时,UIKit对于这个控制器使用了一个展示控制器来管理它各个方面的呈现过程。展示控制器可以添加那些由动画对象提供的自己的动画,它可以响应尺寸大小变化,并且它还可以管理视图控制器是如何呈现在屏幕上的其他方面的事情。

毫无疑问,从官方文档里面我们可以得到如何使用它的一切信息,但是谁又能说我们学习编程不是天生的“弱势群体”了。(ps:多希望我是混血儿啊!)笔者接下来的内容就不再引用官方文档,有需要的朋友请点Apple后面的官方文档。

对于系统默认的pop、push、modal相应的也有默认UIPresentationController对象行为默认的实现,当一个视图控制器被呈现时,UIKit就会调用视图控制器关联的UIPresentationController对象的presentationTransitionWillBegin()方法,当呈现阶段结束后就会调用其presentationTransitionDidEnd(_:)来告诉你过渡结束。对于文章将要给出的Demo在这里面的代码将是这样:

//When a view controller is about to be presented,UIKit calls the presentation controller’s presentationTransitionWillBegin() method.
    override func presentationTransitionWillBegin() {
        dismissView.frame = self.containerView!.bounds
        dismissView.alpha = 0.0

        self.containerView!.addSubview(dismissView)
        self.containerView!.addSubview(self.presentedView()!)

        let transitionCoordinator = self.presentedViewController.transitionCoordinator()!
        transitionCoordinator.animatealongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext) in
            self.dismissView.alpha = 1.0
            },completion: nil)
    }

//At the end of the presentation phase,UIKit calls the presentationTransitionDidEnd(_:) method to let you kNow that the transition finished.
    override func presentationTransitionDidEnd(completed: Bool) {
        if !completed {
            dismissView.removeFromSuperview()
        }
    }

当视图控制器退场时,也会调用UIPresentation对象的dismissalTransitionWillBegin()dismissalTransitionDidEnd(_:)方法,就像这样:

override func dismissalTransitionWillBegin() {
        let transitionCoordinator = self.presentedViewController.transitionCoordinator()!
        transitionCoordinator.animatealongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext) in
            self.dismissView.alpha = 0.0
            },completion: nil)
    }

    override func dismissalTransitionDidEnd(completed: Bool) {
        if completed {
            dismissView.removeFromSuperview()
        }
    }

同时视图控制器被呈现的 view 在过渡动画结束后的最终位置也是由 UIPresentationViewController 来负责给定的,我们只需要重载frameOfPresentedViewInContainerView()就可以了:

override func frameOfPresentedViewInContainerView() -> CGRect {
        var frame = self.containerView!.bounds
        frame = CGRectInset(frame,50.0,200.0)

        return frame
    }

如果你还想让界面的尺寸能适应屏幕的旋转,还需要在viewWillTransitionToSize(size:coordinator:)方法里做出一些改变:

override func viewWillTransitionToSize(size: CGSize,withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransitionToSize(size,withTransitionCoordinator: coordinator)

        coordinator.animatealongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext) in
            self.dismissView.frame = self.containerView!.bounds
            },completion: nil)
    }

(ps:笔者忽略一个细节——整个界面是在storyboard里搭建的,加上用了所有竖屏的sizeClass限定,导致旋转之后就只有transition后的半透明的黑色遮罩能适应屏幕的旋转,其他的都“不翼而飞”,真是抱歉!)

UIViewControllerAnimatedTransitioning

就像我们看到的这样,它不是以明显的OC类名或者部分类名结尾的API。正如各位想得这样,它只是一个协议,一个负责动画过渡的协议。而且它的协议方法也只有两个:

  • transitionDuration(_:):负责返回动画的过渡时间
  • animateTransition(_:):负责动画过渡的具体行为

前者相当直观,笔者不再赘述。后者会传回一个参数——过渡上下文(transitionContext),通过对应的键我们可以访问实现过渡所必须的一些对象:

  • viewControllerForKey:访问过渡前后的视图控制器
  • containerView:访问过渡当前控制器的视图(在哪个控制器里访问就获得哪个控制器的视图)
  • viewForKey:访问过渡前后控制器的视图(与前者的不同在于可以在一个控制器中访问其过渡前后的视图)
  • finalFrameForViewController、initialFrameForViewController:获取过渡前后控制器过渡开始和结束的frame

UIViewControllerTransitioningDelegate

如果你想掌控自己的视图控制器的过渡方式,那么就应该让控制器遵守标题所指的协议,并重新设置它的过渡代理。文章的Demo中将控制器的模态视图的风格设置为自定义,因为Demo展示的正是模态视图的自定义过渡,否则将会和系统的风格冲突,而这将为你展现iOS的“凌乱美”。Demo中使用了如下方法:

  • presentationControllerForPresentedViewController(_:presentingViewController:sourceViewController:):iOS8新增方法,给即将要被呈现的视图控制器自身指定UIPresentationController对象,来管理过渡前后的视图切换
  • animationControllerForPresentedController(_:presentingViewController:sourceViewController:):给即将要被呈现的视图控制器自身指定过渡的动画
  • animationControllerFordismissedController(_:):给即将退场的视图控制器自身指定过渡动画

下面通过几张图片(来源)来说明presentedViewController、presentingViewController:

可以和用户交互的ViewController叫做presentedViewController,也就是过渡的目的视图控制器,在Demo中也就是MessageViewController。presentingViewController就是后面被部分遮盖的UIViewController,即Demo中的ViewController。presentedViewController就是要呈现(presentation)的content,所有的UIViewController的 呈 现 从iOS8开始由UIPresentationController来管理,它定义了content和chrome的动画,chrome可以理解为presentedViewController与presentingViewController的隔离层,即Demo中的半透明的黑色遮罩:

你可能还注意到了sourceController,它其实就是presentingController,可以将它理解为触发过渡的源控制器,Demo中得到了验证:

func presentationControllerForPresentedViewController(presented: UIViewController,presentingViewController presenting: UIViewController,sourceViewController source: UIViewController) -> UIPresentationController? {
        if presented == self {
            //presented: MessageViewController source: ViewController
            print("presented: \(presented),source:\(source)")

            return CustomPresentationController(presentedViewController: presented,presentingViewController: presenting)
        } else {
            return nil
        }
    }

    func animationControllerForPresentedController(presented: UIViewController,presentingController presenting: UIViewController,sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if source == presenting {
            print("equal")
        }
        if presented == self {
            print("presented: \(presented)),presenting: \(presenting) source:\(source)")

            return CustomAnimationController(isPresenting: true)
        } else {
            return nil
        }

    func animationControllerFordismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if dismissed == self {
            //dismissed: MessageViewController
            print("dismissed: \(dismissed)")

            return CustomAnimationController(isPresenting: false)
        } else {
            return nil
        }
    }

最终效果图:

项目源代码在这里,由于SwiftAPI的不稳定,原作者的Demo已经不能正常运行,为此笔者做了新的调整以便正常运行。同时这里有篇扩展资料,是关于自定义导航过渡的文章(OC实现),并讲解了如何构建可交互性的过渡,文章的Demo也很有使用价值,值得花时间去品读!

总结

“告诉我过渡前后的动画方式和所要展示的内容,我就按照你的要求完成过渡”,感谢Apple的UIKit团队所作出的努力,让我们能以清晰的思路和优雅的代码架构完成我们所想要的过渡效果。细想一下,凡此种种,不就是良性App架构的体现吗?

参考:
http://nonomori.farbox.com/post/ios-8-presentation-controller

写在后面的扩展资料:

唐巧的技术博客:iOS视图控制器转场详解

引用其中的一句话:

转场动画的本质是对即将消失的当前视图和即将出现的下一视图进行动画。

自定义过渡的更多相关文章

  1. HTML5 播放 RTSP 视频的实例代码

    目前大多数网络摄像头都是通过 RTSP 协议传输视频流的,但是 HTML 并不标准支持 RTSP 流。本文重点给大家介绍HTML5 播放 RTSP 视频的实例代码,需要的朋友参考下吧

  2. 利用Node实现HTML5离线存储的方法

    这篇文章主要介绍了利用Node实现HTML5离线存储的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. 详解如何通过H5(浏览器/WebView/其他)唤起本地app

    这篇文章主要介绍了详解如何通过H5(浏览器/WebView/其他)唤起本地app的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. H5混合开发app如何升级的方法

    本篇文章主要介绍了H5混合开发app如何升级的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. AmazeUI 折叠面板的实现代码

    这篇文章主要介绍了AmazeUI 折叠面板的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. HTML5手指下滑弹出负一屏阻止移动端浏览器内置下拉刷新功能的实现代码

    这篇文章主要介绍了HTML5手指下滑弹出负一屏阻止移动端浏览器内置下拉刷新功能的实现代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  7. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  8. html5唤起app的方法

    这篇文章主要介绍了html5唤起app的方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. HTML5拍照和摄像机功能实战详解

    这篇文章主要介绍了HTML5拍照和摄像机功能实战详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部