《iOS 10 day by day》是 shinobicontrols 公司编写的系列博客,介绍开发者须要了解的 iOS 10 新特性,每周更新。本系列翻译(文集地址)已取得官方受权。目录点此。仓薯翻译,欢迎指正:)ios
Shinobicontrols 为 iOS 和 Android 开发者提供高性能、响应式的 UI 控件 SDK,尤为是图表方面的控件。 官网 : shinobicontrols.com twitter : @shinobicontrolsgit
用基于 block 的 UIView animation 来编写 view 属性(frame, transform 等等)变化的动画很是简单。只须要短短几行代码:github
view.alpha = 1
UIView.animate(withDuration: 2) {
containerView.alpha = 0
}复制代码
你能够指定动画结束以后调用的 completion block。若是默认的匀速动画不能知足你的要求,还能够调整时间曲线。闭包
可是,若是你须要一种自定义的曲线动画,相应的属性变化首先要快速开始,而后再急速慢下来,该怎么办呢?另一个有点麻烦的问题是,怎么取消正在进行中的动画?虽然这些问题均可以解决,用第三方库或者建立一个新的 animation 来取代进行中的 animation。但苹果在 UIKit 中新加的组件能把这些步骤简化许多:进入UIViewPropertyAnimator
的世界吧!app
UIViewPropertyAnimator
的 API 设计得很完善,可扩展性也很好。它 cover 了传统 UIView animation 动画的绝大部分功能,而且大大加强了你对动画过程的掌控能力。具体来讲,你能够在动画过程当中任意时刻暂停,能够随后再选择继续,甚至还能在动画过程当中动态改变更画的属性(例如,原本动画终点在屏幕左下角的,能够在动画过程当中把终点改到右上角)。iview
为了探索这个新的类,咱们来看几个例子,这几个例子都是演示一张图片划过屏幕的动画。如同全部 Day by Day 系列的文章,例子的代码能够在 Github 上下载到。此次咱们用的是 Playground。编辑器
咱们全部的 playground 页面都是让一个小忍者划过屏幕的动画。为了方便对比这些页面的代码,咱们把公共部分的代码藏在 Sources
文件夹里。这样不只能简化每一个页面的代码,还能加快编译过程,由于 Sources
里的代码是预编译过的。ide
Sources
里包含一个简单的UIView
子类,叫作NinjaContainerView
。它的惟一功能就是添加一个 UIImageView
做为子 view,来显示咱们的小忍者。我把忍者图片加到了 Resources
里。函数
import UIKit
public class NinjaContainerView: UIView {
public let ninja: UIImageView = {
let image = UIImage(named: "ninja")
let view = UIImageView(image: image)
view.frame = CGRect(x: 0, y: 0, width: 45, height: 39)
return view
}()
public override init(frame: CGRect) {
// Animating view
super.init(frame: frame)
// Position ninja in the bottom left of the view
ninja.center = {
let x = (frame.minX + ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
// Add image to the container
addSubview(ninja)
backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Moves the ninja view to the bottom right of its container, positioned just inside.
public func moveNinjaToBottomRight() {
ninja.center = {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
}
}复制代码
如今,在每一个 playground 页面里,咱们能够复制粘贴如下代码:工具
import UIKit
import PlaygroundSupport
// Container for our animating view
let containerView = NinjaContainerView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
let ninja = containerView.ninja
// Show the container view in the Assistant Editor
PlaygroundPage.current.liveView = containerView复制代码
这样咱们就能够用上 Playground 强大的 "Live View" 功能,不用启动 iOS 模拟器就能够展现动画效果。尽管 Playground 仍是有些很差用的地方,但用来尝试新功能是很是合适的。
要显示 Live View,点击菜单栏上的 View -> Assistant Editor -> Show Assistant Editor,或者点击右上角工具栏里两环相套的图标。若是在右半边的编辑器里没有看到 live view,要确保选中的是 Timeline 而不是 Manual —— 不得不认可我在这里浪费了一点时间。
UIViewPropertyAnimator
的用法能够跟传统的 animation block 同样:
UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
containerView.moveNinjaToBottomRight()
}.startAnimation()复制代码
这会触发一个时长为 1 秒,时间曲线是缓进缓出的动画。动画的内容是闭包里的部分。
注意咱们是经过调用 startAnimation()
来显式启动动画的。另一种建立 animator 的方法能够不用手动启动动画,就是 runningPropertyAnimator(withDuration:delay:options:animations:completion:)
。确实有点长,因此可能还不如用第一种。
先建立好 animator ,再往上添加动画也很容易:
// view 设置好以后,咱们先来一个简单的动画
let animator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut)
// 添加第一个 animation block
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
// 而后再加第二个
animator.addAnimations {
ninja.alpha = 0
}复制代码
这两个 animation block 会同时进行。
添加 completion block 的方法也很相似:
animator.addCompletion {
_ in
print("Animation completed")
}
animator.addCompletion {
position in
switch position {
case .end: print("Completion handler called at end of animation")
case .current: print("Completion handler called mid-way through animation")
case .start: print("Completion handler called at start of animation")
}
}复制代码
若是动画完整跑完的话,咱们能够在控制台看到如下信息:
Animation completed
Completion handler called at end of animation复制代码
咱们能够利用 animator 让动画跟随拖拽的进度进行:
let animator = UIViewPropertyAnimator(duration: 5, curve: .easeIn)
// Add our first animation block
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
let scrubber = UISlider(frame: CGRect(x: 0, y: 0, width: containerView.frame.width, height: 50))
containerView.addSubview(scrubber)
let eventListener = EventListener()
eventListener.eventFired = {
animator.fractionComplete = CGFloat(scrubber.value)
}
scrubber.addTarget(eventListener, action: #selector(EventListener.handleEvent), for: .valueChanged)复制代码
Playground 整体来讲是很好用的,并且还能在 Live View 里面添加可交互的 UI 控件。然而,接受响应事件就有点麻烦,由于咱们须要一个
NSObject
的子类来监听诸如.valueChanged
这种事件。因此,咱们简单建立一个EventListener
,一旦触发它的handleEvent
方法,它会调用咱们的eventFired
闭包。
这里 fractionComplete
值的计算方法跟时间没有关系了,因此咱们的小忍者再也不像以前指定的同样,会优雅地缓动。
Property animator 最强大的功能体如今它能随时打断正在进行的动画。让动画反向也很是容易,只需设置 isReversed
属性便可。
为了演示这一点,咱们使用关键帧动画,这样就能够制做一个多阶段的动画了:
animator.addAnimations {
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
ninja.center = containerView.center
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
containerView.moveNinjaToBottomRight()
}
})
}
let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 30)))
button.setTitle("Reverse", for: .normal)
button.setTitleColor(.black(), for: .normal)
button.setTitleColor(.gray(), for: .highlighted)
let listener = EventListener()
listener.eventFired = {
animator.isReversed = true
}
button.addTarget(listener, action: #selector(EventListener.handleEvent), for: .touchUpInside)
containerView.addSubview(button)
animator.startAnimation()复制代码
按下按钮的时候,animator 就会把动画反向进行,只要这一时刻动画还没结束。
Property animator 在简洁优美的同时,还有很强的扩展性。若是你须要在苹果提供的时间函数以外自定义另外一种时间曲线,只需传进一个实现 UITimingCurveProvider
协议的对象。大部分状况下用到的是 UICubicTimingParameters
或者 UISpringTimingParameters
。
例如,咱们想让小忍者在划过屏幕的过程当中,先快速加速,而后再慢慢中止。以下图的贝塞尔曲线所示(绘制曲线用了这个很方便的在线工具):
let bezierParams = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.05, y: 0.95),
controlPoint2: CGPoint(x: 0.15, y: 0.95))
let animator = UIViewPropertyAnimator(duration: 4, timingParameters:bezierParams)
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
animator.startAnimation()复制代码
新的 property animator 让编写动画更简单,它的 API 跟传统方法相似,还添加了打断动画、自定义时间曲线等功能。
Apple 为 UIViewPropertyAnimator
提供了详尽的文档。另外,也能够看看这场 WWDC 视频,深度解读这些新的 API,还讲了怎么用新的 API 来作 viewController 跳转的过渡动画。另外还有一些有趣的例子,例如一些简单的游戏。
原文地址:iOS 10 Day by Day :: Day 4 :: UIViewPropertyAnimator
原做者:Sam Burnstone @sam_burnstone
ShinobiControls 官网:ShinobiControls.com twitter : @shinobicontrols
译者:戴仓薯