iOS 自己定义页面的切换动画与交互动画 By Swift

在iOS7之前,开发人员为了寻求自己定义Navigation Controller的Push/Pop动画,仅仅能受限于子类化一个UINavigationController,或是用自己定义的动画去覆盖它。可是随着iOS7的到来,Apple针对开发人员推出了新的工具,以更灵活地方式管理UIViewController切换。

我把终于的Demo稍做改动,算是找了一个合适的应用场景,另外配上几张美图,拉拉人气

尽管是Swift的Demo,可是转成Objective-C相当easy。

终于效果预览:

自己定义导航栏的Push/Pop动画

为了在基于UINavigationController下做自己定义的动画切换,先建立一个简单的project,这个project的rootViewController是一个UINavigationController,UINavigationController的rootViewController是一个简单的UIViewController(称之为主页面),通过这个UIViewController上的一个Button能进入到下一个UIViewController中(称之为详情页面),我们先在主页面的ViewController上实现两个协议:UINavigationControllerDelegate和UIViewControllerAnimatedTransitioning,然后在ViewDidLoad里面把navigationController的delegate设为self,这样在导航栏Push和Pop的时候我们就知道了,然后用一个属性记下是Push还是Pop,就像这样:

func navigationController(navigationController: UINavigationController!, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController!, toViewController toVC: UIViewController!) -> UIViewControllerAnimatedTransitioning! {

navigationOperation = operation

return self

}

这是iOS7的新方法,这种方法须要你提供一个UIViewControllerAnimatedTransitioning,那UIViewControllerAnimatedTransitioning究竟是什么呢?

UIViewControllerAnimatedTransitioning是苹果新添加的一个协议,其目的是在须要使用自己定义动画的同一时候,又不影响视图的其它属性,让你把焦点集中在动画实现的本身上,然后通过在这个协议的回调里编写自己定义的动画代码,即“切换中应该会发生什么”,负责切换的详细内容,不论什么实现了这一协议的对象被称之为动画控制器。你能够借助协议能被不论什么对象实现的这一特性,从而把各种动画效果封装到不同的类中,仅仅要方便使用和管理,你能够发挥一切手段。我在这里让主页面实现动画控制器也是能够的,由于它是导航栏的rootViewController,会一直存在,我仅仅要在里面编写自己定义的Push和Pop动画代码就能够了:

//UIViewControllerTransitioningDelegate

func transitionDuration(transitionContext: UIViewControllerContextTransitioning!) -> NSTimeInterval {

return 0.4

}

func animateTransition(transitionContext: UIViewControllerContextTransitioning!) {

let containerView = transitionContext.containerView()

let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)

let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)

var destView: UIView!

var destTransform: CGAffineTransform!

if navigationOperation == UINavigationControllerOperation.Push {

containerView.insertSubview(toViewController.view, aboveSubview: fromViewController.view)

destView = toViewController.view

destView.transform = CGAffineTransformMakeScale(0.1, 0.1)

destTransform = CGAffineTransformMakeScale(1, 1)

} else if navigationOperation == UINavigationControllerOperation.Pop {

containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)

destView = fromViewController.view

// 假设IDE是Xcode6 Beta4+iOS8SDK,那么在此处设置为0,动画将不会被运行(不确定是哪里的Bug)

destTransform = CGAffineTransformMakeScale(0.1, 0.1)

}

UIView.animateWithDuration(transitionDuration(transitionContext), animations: {

destView.transform = destTransform

}, completion: ({completed in

transitionContext.completeTransition(true)

}))

}

上面第一个方法返回动画持续的时间,而以下这种方法才是详细须要实现动画的地方。UIViewControllerAnimatedTransitioning的协议都包括一个对象:transitionContext,通过这个对象能获取到切换时的上下文信息,比方从哪个VC切换到哪个VC等。我们从transitionContext获取containerView,这是一个特殊的容器,切换时的动画将在这个容器中进行;UITransitionContextFromViewControllerKey和UITransitionContextToViewControllerKey就是从哪个VC切换到哪个VC,easy理解;除此之外,还有直接获取view的UITransitionContextFromViewKey和UITransitionContextToViewKey等。

我按Push和Pop把动画简单的区分了一下,Push时scale由小变大,Pop时scale由大变小,不同的操作,toViewController的视图层次也不一样。最后,在动画完毕的时候调用completeTransition,告诉transitionContext你的动画已经结束,这是很重要的方法,必须调用。在动画结束时没有对containerView的子视图进行清理(比方把fromViewController的view移除掉)是由于transitionContext会自己主动清理,所以我们无须在额外处理。

注意一点,这样一来会发现原来导航栏的交互式返回效果没有了,假设你想用原来的交互式返回效果的话,在返回动画控制器的delegate方法里返回nil,如:

if operation == UINavigationControllerOperation.Push {

navigationOperation = operation

return self

}

return nil

然后在viewDidLoad里,Objective-C直接self.navigationController.interactivePopGestureRecognizer.delegat = self就可以了,Swift除了要navigationController.interactivePopGestureRecognizer.delegate = self之外,还要在self上声明实现了UIGestureRecognizerDelegate这个协议,尽管实际上你并没有实现。

一个简单的自己定义导航栏Push/Pop动画就完毕了。

自己定义Modal的Present/Dismiss动画

自己定义Modal的Present与Dismiss动画与之前相似,都须要提供一个动画管理器,我们用详情页面来展示一个Modal页面,详情页面就作为动画管理器:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning!) -> NSTimeInterval {

return 0.6

}

func animateTransition(transitionContext: UIViewControllerContextTransitioning!) {

let containerView = transitionContext.containerView()

let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)

let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)

var destView: UIView!

var destTransfrom = CGAffineTransformIdentity

let screenHeight = UIScreen.mainScreen().bounds.size.height

if modalPresentingType == ModalPresentingType.Present {

destView = toViewController.view

destView.transform = CGAffineTransformMakeTranslation(0, screenHeight)

containerView.addSubview(toViewController.view)

} else if modalPresentingType == ModalPresentingType.Dismiss {

destView = fromViewController.view

destTransfrom = CGAffineTransformMakeTranslation(0, screenHeight)

containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)

}

UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0,

options: UIViewAnimationOptions.CurveLinear, animations: {

destView.transform = destTransfrom

}, completion: {completed in

transitionContext.completeTransition(true)

})

}

动画部分用了一个iOS7的弹簧动画,usingSpringWithDamping的值设置得越小,弹的就越明显,动画的其它地方与之前相似,不一样的是之前主页面除了做动画管理器之外,还实现了UINavigationControllerDelegate协议,由于我们是自己定义导航栏的动画,而在这里须要自己定义Modal动画就要实现还有一个协议:UIViewControllerTransitioningDelegate,这个协议与之前的UINavigationControllerDelegate协议具有相似性,都是返回一个动画管理器,iOS7的方法总共同拥有四个,有两个交互式的先无论,我们仅仅须要实现另两个就可以:

func animationControllerForPresentedController(presented: UIViewController!, presentingController presenting: UIViewController!, sourceController source: UIViewController!) -> UIViewControllerAnimatedTransitioning! {

modalPresentingType = ModalPresentingType.Present

return self

}

func animationControllerForDismissedController(dismissed: UIViewController!) -> UIViewControllerAnimatedTransitioning! {

modalPresentingType = ModalPresentingType.Dismiss

return self

}

我相同的用一个属性记下是Present还是Dismiss,然后返回self。由于我是用的Storyboard,所以须要在prepareForSegue方法里设置一下transitionDelegate:

override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {

let modal = segue.destinationViewController as UIViewController

modal.transitioningDelegate = self

}

对须要运行自己定义动画的VC设置transitionDelegate属性就可以。

如此一来,一个针对模态VC的自己定义动画也完毕了。

自己定义导航栏的交互式动画

与动画控制器相似,我们把实现了UIViewControllerInteractiveTransitioning协议的对象称之为交互控制器,最经常使用的就是把交互控制器应用到导航栏的Back手势返回上,而假设要实现一个自己定义的交互式动画,我们有两种方式来完毕:实现一个交互控制器,或者使用iOS提供的UIPercentDrivenInteractiveTransition类作交互控制器。

使用UIPercentDrivenInteractiveTransition

我们这里就用UIPercentDrivenInteractiveTransition来完毕导航栏的交互式动画。先看下UIPercentDrivenInteractiveTransition的定义:

实际上这个类就是实现了UIViewControllerInteractiveTransitioning协议的交互控制器,我们使用它就能够轻松地为动画控制器加入一个交互动画。调用updateInteractiveTransition:更新进度;调用cancelInteractiveTransition取消交互,返回到切换前的状态;调用finishInteractiveTransition通知上下文交互已完毕,同completeTransition一样。我们把交互动画应用到详情页面Back回主页面的地方,由于之前的动画管理器的角色是主页面担任的,Navigation Controller的delegate同一时间仅仅能有一个,那在这里交互控制器的角色也由主页面来担任。首先加入一个手势识别器:

let popRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: Selector("handlePopRecognizer:"))

popRecognizer.edges = UIRectEdge.Left

self.navigationController.view.addGestureRecognizer(popRecognizer)

UIScreenEdgePanGestureRecognizer继承于UIPanGestureRecognizer,能检測从屏幕边缘滑动的手势,设置edges为left检測左边就可以。然后实现handlePopRecognizer:

func handlePopRecognizer(popRecognizer: UIScreenEdgePanGestureRecognizer) {

var progress = popRecognizer.translationInView(navigationController.view).x / navigationController.view.bounds.size.width

progress = min(1.0, max(0.0, progress))

println("\(progress)")

if popRecognizer.state == UIGestureRecognizerState.Began {

println("Began")

self.interactivePopTransition = UIPercentDrivenInteractiveTransition()

self.navigationController.popViewControllerAnimated(true)

} else if popRecognizer.state == UIGestureRecognizerState.Changed {

self.interactivePopTransition?.updateInteractiveTransition(progress)

println("Changed")

} else if popRecognizer.state == UIGestureRecognizerState.Ended || popRecognizer.state == UIGestureRecognizerState.Cancelled {

if progress > 0.5 {

self.interactivePopTransition?.finishInteractiveTransition()

} else {

self.interactivePopTransition?.cancelInteractiveTransition()

}

println("Ended || Cancelled")

self.interactivePopTransition = nil

}

}

我用了一个实例变量引用UIPercentDrivenInteractiveTransition,这个类仅仅在须要用时才创建,否则在正常Push/Pop的时候,即使仅仅是点击操作并没有识别手势的情况下,也会进入交互(你也能够在要求你返回交互控制器时,进行一些推断,通过返回nil来屏蔽,但这显然就太麻烦了)。当手势识别的时候我们调用pop,用户手势发生变化时,调用update去更新,无论是end还是cancel,都推断下是进入下一个页面还是返回之前的页面,完毕这一切后把交互控制器清理掉。

如今我们已经有了交互控制器对象,仅仅须要把它给告知给Navigation Controller就可以了,我们实现UINavigationControllerDelegate的还有一个方法:

func navigationController(navigationController: UINavigationController!, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning!) -> UIViewControllerInteractiveTransitioning! {

return self.interactivePopTransition

}

我们从详情页面通过自己定义的交互动画返回到上一个页面的工作就完毕了。

Demo效果预览:

使用UIPercentDrivenInteractiveTransition的Demo

自己定义交互控制器

我在之前提过,UIPercentDrivenInteractiveTransition实际上就是实现了UIViewControllerInteractiveTransitioning协议,仅仅要是实现了这个协议的对象就能够称之为交互控制器,我们假设想更加精确的管理动画以及深入理解处理上的细节,就须要自己实现UIViewControllerInteractiveTransitioning协议。

UIViewControllerInteractiveTransitioning协议总共同拥有三个方法,当中startInteractiveTransition:是必须实现的方法,我们在里面初始化动画的状态:

func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning!) {

self.transitionContext = transitionContext

let containerView = transitionContext.containerView()

let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)

let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)

containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)

self.transitingView = fromViewController.view

}

这里不涉及动画,仅仅是把须要切换的view加入到上下文环境中就可以。动画部分我们还是和之前使用UIPercentDrivenInteractiveTransition的接口保持一致,加入几个方法:

func updateWithPercent(percent: CGFloat) {

let scale = CGFloat(fabsf(Float(percent - CGFloat(1.0))))

transitingView?.transform = CGAffineTransformMakeScale(scale, scale)

transitionContext?.updateInteractiveTransition(percent)

}

func finishBy(cancelled: Bool) {

if cancelled {

UIView.animateWithDuration(0.4, animations: {

self.transitingView!.transform = CGAffineTransformIdentity

}, completion: {completed in

self.transitionContext!.cancelInteractiveTransition()

self.transitionContext!.completeTransition(false)

})

} else {

UIView.animateWithDuration(0.4, animations: {

print(self.transitingView)

self.transitingView!.transform = CGAffineTransformMakeScale(0, 0)

print(self.transitingView)

}, completion: {completed in

self.transitionContext!.finishInteractiveTransition()

self.transitionContext!.completeTransition(true)

})

}

}

updateWithPercent:方法用来更新view的transform属性,finishBy:方法主要用来推断是进入下一个页面还是返回到之前的页面,并告知transitionContext眼下的状态,以及对当前正在scale的view做最后的动画。这里的transitionContext和transitingView能够在前面的处理手势识别代码中取得,我将里面的代码更新了一下,变成以下这样:

func handlePopRecognizer(popRecognizer: UIScreenEdgePanGestureRecognizer) {

var progress = popRecognizer.translationInView(navigationController.view).x / navigationController.view.bounds.size.width

progress = min(1.0, max(0.0, progress))

println("\(progress)")

if popRecognizer.state == UIGestureRecognizerState.Began {

println("Began")

isTransiting = true

//self.interactivePopTransition = UIPercentDrivenInteractiveTransition()

self.navigationController.popViewControllerAnimated(true)

} else if popRecognizer.state == UIGestureRecognizerState.Changed {

//self.interactivePopTransition?.updateInteractiveTransition(progress)

updateWithPercent(progress)

println("Changed")

} else if popRecognizer.state == UIGestureRecognizerState.Ended || popRecognizer.state == UIGestureRecognizerState.Cancelled {

//if progress > 0.5 {

//    self.interactivePopTransition?.finishInteractiveTransition()

//} else {

//    self.interactivePopTransition?.cancelInteractiveTransition()

//}

finishBy(progress < 0.5)

println("Ended || Cancelled")

isTransiting = false

//self.interactivePopTransition = nil

}

}

另外还用一个额外布尔值变量isTransiting来标识当前是否在手势识别中,这是为了在返回交互控制器的时候,不会在不当的时候返回self:

func navigationController(navigationController: UINavigationController!, interactionControllerForAnimationController animationController:

UIViewControllerAnimatedTransitioning!) -> UIViewControllerInteractiveTransitioning! {

if !self.isTransiting {

return nil

}

return self

}

这样一来就完毕了自己定义交互控制器。能够发现,基本流程与使用UIPercentDrivenInteractiveTransition是一致的,UIPercentDrivenInteractiveTransition主要是帮我们封装了transitionContext的初始化以及对它的调用等,仅仅是动画部分须要我们在额外处理一下了。

使用自己定义交互控制器的Demo

终于效果:

我在主页面上多放了几个带Image的Button,在点击Button时会将Button的Image传递到详情页面,详情页面相应的也有一个UIImageView用来显示。在主页面初始化动画状态的时候,会生成一个Image的快照来进行动画,要是在曾经,我们仅仅能通过UIGraphics的APIs进行一系列的操作,涉及视图的scale、旋转、透明及渲染到context等,但如今,我们仅仅须要用iOS7的API就可以了:

@availability(iOS, introduced=7.0)

func snapshotViewAfterScreenUpdates(afterUpdates: Bool) -> UIView

这个API能帮助我们高速获取一个视图的的快照,afterUpdates參数表示是否等全部效果应用到该视图之后再获取,假设设置为false,则马上获取;为true则会受到后面对该视图的影响。

在动画之前,把主页面和详情页面相应的Button和ImageView隐藏,然后对快照生成的View进行动画,动画用简单的frame隐式动画就能够了。

终于效果的Demo(上传到我的资源页面时总是失败,所以仅仅能上传到GitHub上了)

最后附上一张图,这个图比較easy区分那几个名称相近的协议:

UPDATED:

GitHub上已更新至Xcode 6,主要是语法上的一些小调整

时间: 2024-10-09 17:34:46

iOS 自己定义页面的切换动画与交互动画 By Swift的相关文章

iOS 自定义页面的切换动画与交互动画

在iOS7之前,开发者为了寻求自定义Navigation Controller的Push/Pop动画,只能受限于子类化一个UINavigationController,或是用自定义的动画去覆盖它.但是随着iOS7的到来,Apple针对开发者推出了新的工具,以更灵活地方式管理UIViewController切换. 自定义导航栏的Push/Pop动画 为了在基于UINavigationController下做自定义的动画切换,先建立一个简单的工程,这个工程的rootViewController是一个

iOS 自定义页面的切换动画与交互动画 By Swift(转)

交互动画切换动画Swiftiosios7 目录(?)[-] 最终效果预览 自定义导航栏的PushPop动画 自定义Modal的PresentDismiss动画 自定义导航栏的交互式动画 使用UIPercentDrivenInteractiveTransition 自定义交互控制器 最终效果 UPDATED 在iOS7之前,开发者为了寻求自定义Navigation Controller的Push/Pop动画,只能受限于子类化一个UINavigationController,或是用自定义的动画去覆盖

iOS——使用StroryBoard页面跳转及传值

之前在网上搜iOS的页面跳转大多都是按回以前的那种xib的形式,但鄙人是使用storyboard的.这篇就只介绍利用storyboard进行页面跳转与传值. 新建页面 iOS的程序也是使用了MVC的思想,页面文件与代码文件是分离的,这点与Android的类似.在使用storyboard的方式中,新建页面只需要在storyboard中拖入一个View Controller则可, 接下来就可以在新建的页面中添加各种控件来编辑这个新建的页面. 在新建的页面上编排各种视图控件如同在Android中编辑那

iOS使用StroryBoard页面跳转及传值

之前在网上iOS的页面跳转大多都是按回以前的那种xib的形式,但鄙人是使用storyboard的.这篇就只介绍利用storyboard进行页面跳转与传值. 新建页面 iOS的程序也是使用了MVC的思想,页面文件与代码文件是分离的,这点与Android的类似.在使用storyboard的方式中,新建页面只需要在storyboard中拖入一个View Controller则可, 接下来就可以在新建的页面中添加各种控件来编辑这个新建的页面. 在新建的页面上编排各种视图控件如同在Android中编辑那个

Android--ViewPager多页面滑动切换以及动画效果

背景                                                                                           ViewPager.它是google SDk中自带的一个附加包的一个类,可以用来实现屏幕间的切换.这个附加包是android-support-v4.jar. 根据屏幕的分辨率和图片的宽度计算动画移动的偏移量 代码                                                   

iOS动画1 — UIView动画

iOS动画1 — UIView动画 iOS动画基础是Core Animation核心动画.Core Animation是iOS平台上负责图形渲染与动画的基础设施.由于核心动画的实现比较复杂,苹果提供了实现简单动画的接口—UIView动画.UIView动画封装在UIView的Category中,主要实现一些简单和常用的动画.UIView动画是对核心动画进行了一层封装,所以最终动画还是通过Core Animation的接口实现. 主要的动画效果都可以通过UIView动画和Core Animation

velocity.js实现页面滚动切换效果

在线演示1 在线演示2 在线演示3 在线演示4 在线演示5 本地下载 原文链接:http://www.gbtags.com/gb/share/5650.htm 页面滚动切换效果 今天介绍一个Javascript的小型的动画插件velocity.js,可以方便高效的开发一个具有多页面滚动切换效果的网站. 浏览器支持 velocity.js支持IE8+.Chrome.Firefox等浏览器,并支持Andriod以及IOS. 我们开发一个工程,里面有一组相关联系的大型页面.不能做到在一张页面中把它们展

iOS核心动画以及UIView动画的介绍

我们看到很多App带有绚丽狂拽的特效,别出心裁的控件设计,很大程度上提高了用户体验,在增加了实用性的同时,也赋予了app无限的生命力.这些华丽的效果很多都是基于iOS的核心动画原理实现的,本文介绍一些iOS开发中最基本的动画效果实现,掌握了基本属性,才可以绘制出更华丽的效果. 一.概念扩充  1.核心动画: Core Animation,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍. Core Animation可以用在Mac OS X和iOS平台.在iO

iOS Core Animation Advanced Techniques(四):隐式动画和显式动画

隐式动画 按照我的意思去做,而不是我说的. -- 埃德娜,辛普森 我们在第一部分讨论了Core Animation除了动画之外可以做到的任何事情.但是动画师Core Animation库一个非常显著的特性.这一章我们来看看它是怎么做到的.具体来说,我们先来讨论框架自动完成的隐式动画(除非你明确禁用了这个功能). 事务 Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画.动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在.