iOS 自定义页面的切换动画与交互动画 By Swift
iOS7之前,开发者为了寻求自定义Navigation Controller的Push/Pop动画,只能受限于子类化一个UINavigationController,或是用自定义的动画去覆盖它。但是随着iOS7的到来,Apple针对开发者推出了新的工具,以更灵活地方式管理UIViewController切换。
我把最终的Demo稍做修改,算是找了一个合适的应用场景,另外配上几张美图,拉拉人气。
虽然是Swift的Demo,但是转成Objective-C相当容易。
最终效果预览:
自定义导航栏的Push/Pop动画
为了在基于UINavigationController下做自定义的动画切换,先建立一个简单的工程,这个工程的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,容易理解;除此之外,还有直接获取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动画
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)
})
}
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
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let modal = segue.destinationViewController as UIViewController
modal.transitioningDelegate = self
}
自定义导航栏的交互式动画
使用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
自定义交互控制器
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
}
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)
})
}
}
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
}
}
func navigationController(navigationController: UINavigationController!, interactionControllerForAnimationController animationController:
UIViewControllerAnimatedTransitioning!) -> UIViewControllerInteractiveTransitioning! {
if !self.isTransiting {
return nil
}
return self
}
最终效果:
@availability(iOS, introduced=7.0)
func snapshotViewAfterScreenUpdates(afterUpdates: Bool) -> UIView
最后附上一张图,这个图比较容易区分那几个名称相近的协议:
UPDATED:
原文转之:http://blog.csdn.net/zhangao0086/article/details/38459937
iOS 自定义页面的切换动画与交互动画 By Swift相关推荐
- iOS添加自定义转场动画和交互动画(一)
准备写两篇,第一篇介绍下转场动画,第二篇介绍下我封装的一个转场动画的库,可以很简便的给VC之间的转变加上自定义动画. iOS场景对应的类是ViewController,基本上一个场景对应一个VC,从一 ...
- iOS 在TabBarController视图切换的时候添加动画
iOS中TabBarController是很强大的控制器,使用起来也非常的方便,但是它点击tabbaritem切换视图时却不像navigation一样有种划入划出的效果,那么怎么在TabBarCont ...
- H5实现android、ios分享页面打开特定app的交互
目录 一.判断ios环境.android环境.微信环境.PC环境 二.判断环境.打开app.ios跳转appstore.安卓在微信里面不能直接打开app.需要做一个指引页.指引用户打开浏览器打开app ...
- iOS 自定义转场动画浅谈
代码地址如下: http://www.demodashi.com/demo/11612.html 路漫漫其修远兮,吾将上下而求索 前记 想研究自定义转场动画很久了,时间就像海绵,挤一挤还是有的,花了差 ...
- swiftui动画之tab自定义切换动画_vue 基础-动画过渡 transition 示例
前言 <vue 基础>系列是再次回炉 vue 记的笔记,除了官网那部分知识点外,还会加入自己的一些理解.(里面会有大部分和官网相同的文案,有经验的同学择感兴趣的阅读) 讲到动画,说真的我自 ...
- iOS自定义过渡动画
历时5天从各种英文教程中学习到的过渡动画,是一个很难忘的探索经历 比较好的参考文章自定义UIViewController过渡入门 ,动画入门. 转场方式 首先让我们来了解iOS转场的方式: UINav ...
- vue 页面切换动画_Flutter Hero动画让你的APP页面切换充满动效 不一样的体验 不一样的细节处理...
优美的应用体验 来自于细节的处理,更源自于码农的自我要求与努力,当然也需要码农年轻灵活的思维. 本文章实现的Demo效果,如下图所示: class HeroHomePage extends State ...
- iOS 自定义下拉线条动画
本文摘录自 A GUIDE TO IOS ANIMATION,中文名:<Kitten 的 iOS 动画学习手册>.这是一本非常有趣地介绍 iOS 动画的交互式电子书,提供生动的可交互式元素 ...
- android切换页面上滑动动画,Android ViewPager多页面滑动切换以及动画效果
评论 #28楼[楼主] 2012-06-01 14:27D.Winter @孤寒江雪 我猜 要么在头尾各再加入一个页卡 在页卡切换监听中判断,如果选中了头尾的页卡,就返回到相邻的那个页卡.头尾页卡的界 ...
最新文章
- AlexeyAB DarkNet YOLOv3框架解析与应用实践(三)
- 2021年大数据常用语言Scala(十):基础语法学习 方法
- 创建一个表单,输入数据并且存入到数据库
- pg数据库与MySQL的count函数_postgresql数据库连接数和状态查询操作
- 用Python实现一个大数据搜索引擎
- angularjs 路由 传参
- Zookeeper: Zookeeper架构及FastLeaderElection机制
- 【Verilog HDL】命名的规则研究
- eslint 保存自动格式化_代码规范之理解ESLint、Prettier、EditorConfig
- java swing 字体_Java Swing界面编程(4)---获取本地字体
- NioEventLoopGroup 源码分析
- 定时器计数器工作方式
- wps多人协作的意义_全民皆扁平?WPS时隔六年更新图标,W却变胖了…
- Abaqus汉化问题
- CPAN下载安装pm包方法
- 微信广告转化统计java,百度推广oCPC微信号复制转化次数统计系统数据接口
- 人工智能、机器学习、深度学习从入门到进阶学习资料整理
- 设置谷歌浏览器安全级别
- Phab2 Pro体验 Tango技术简介
- 三次技术转型,程序员的北漂奋斗史 | 程序员有话说