过渡是可以应用于视图的预定义动画。 这些预定义的动画不会尝试在视图的开始和结束状态之间进行插值(就像您在前两章中创建的动画一样)。 相反,您将使用过渡API设计动画,以便UI中的各种更改显得自然。
转换示例(Example transitions)
为了更好地了解何时使用过渡,本节将向您介绍可以使用过渡动画的各种动画场景。
添加新的视图
要在屏幕上添加新视图的动画,可以调用类似于前面章节中使用的方法。 这次的不同之处在于,您将选择一个预定义的过渡效果,并为动画容器视图设置动画。
过度为容器视图设置动画,并在动画运行时显示作为子视图添加到其中的任何新视图。
为了更好地解释如何为容器视图设置动画,以及何时执行子视图之间的转换,请阅读以下代码段(您无需在任何位置键入此代码):
var animationContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//set up the animation container
animationContainerView = UIView(frame: view.bounds)
animationContainerView.frame = view.bounds
view.addSubview(animationContainerView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//create new view
let newView = UIImageView(image: UIImage(named: "banner"))
newView.center = animationContainerView.center
//add the new view via transition
UIView.transition(with: animationContainerView,
duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
self.animationContainerView.addSubview(newView)
},
completion: nil
)
}
(在给animationContainerView设置frame的时候,作者好像重复设置了,不知道是误写还是我理解错了,请评论指出 ?)
在此假设情况下,您将在视图控制器的viewDidLoad()中创建名为animationContainerView的新视图。 然后,您可以将此容器定位并添加到视图中。
稍后,当您想要创建动画过渡时,可以创建一个新的动画视图; 这里它被命名为newView。
要创建转换,请调用transition(with:duration:options:animations:completion : )。 这几乎与标准的UIView动画方法完全相同,但在这种情况下,您提供了一个额外的参数视图,该视图用作过渡动画的容器视图。
这里有一个新的动画选项,.transitionFlipFromBottom,你还没有看到。 这是本章介绍中讨论的预定义转换之一。 .transitionFlipFromBottom翻转视图,视图的下边缘作为视图翻转的“铰链”。
最后,在动画块中的动画容器中添加子视图,这会导致子视图在过渡期间出现。
预定义过渡动画选项的完整列表如下:
- .transitionFlipFromLeft
- .transitionFlipFromRight
- .transitionCurlUp
- .transitionCurlDown
- .transitionCrossDissolve • .transitionFlipFromTop
- .transitionFlipFromBottom
删除视图
使用过渡动画从屏幕上删除子视图与添加子视图非常相似。 要使用过渡动画执行此操作,您只需在动画闭包表达式中调用removeFromSuperview()(同样,这是一个示例,您无需输入此代码):
//remove the view via transition
UIView.transition(with: animationContainerView, duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
someView.removeFromSuperview()
},
completion: nil
)
包装器过渡将执行翻转动画,一些视图将在结束时消失。
隐藏/显示视图(Hiding/showing a view)
到目前为止,在本章中,您只学习了改变视图层次结构过渡。 这就是为什么你需要一个容器视图来进行过渡 - 这会将层次结构更改放在上下文中。
相反,您无需担心设置容器视图来隐藏和显示视图。 在这种情况下,转换使用视图本身作为动画容器。
请考虑以下代码以使用转换隐藏子视图:
//hide the view via transition
UIView.transition(with: someView, duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
someView.alpha = 0
},
completion: { _ in
someView.isHidden = true
}
)
在这里,您将要打开或隐藏的视图作为transition(with:duration:options:animations:completion:).第一个转换参数。 之后你所做的就是在动画块中设置视图的alpha属性( All you do after that is set the alpha property of your view in the animations block and voilà 如有翻译错误请纠正),然后转换动画开始。
更换视图(Replacing a view with another view)
//replace via transition
UIView.transition(from: oldView, to: newView, duration: 0.33,
options: .transitionFlipFromTop, completion: nil)
很明显UIKit会为你做多少重物!
在本章的其余部分中,您将通过转换来展示和隐藏UI元素,并学习一些可以带入您自己项目的新动画技能!
混合过渡(Mixing in transitions)
您将继续参与本章中的Bahama Air登录屏幕项目; 您已经在此屏幕上为视图创建了许多引人注目的动画,以便在登录表单中添加一些功能,并使按钮对被点击做出反应。
接下来,您将模拟一些用户身份验证并为几个不同的进度消息添加动画。 一旦用户点击登录按钮,您将向他们显示消息,包括“正在连接…”,“授权…”和“失败”。
添加以下全局变量到你的项目中:
let status = UIImageView(image: UIImage(named: "banner"))
let label = UILabel()
let messages = ["Connecting ...", "Authorizing ...", "Sending credentials ...", "Failed"]
在viewDidLoad()中添加以下代码:
status.isHidden = true
status.center = loginButton.center
view.addSubview(status)
label.frame = CGRect(x: 0.0, y: 0.0, width: status.frame.size.width, height:status.frame.size.height)
label.font = UIFont(name: "HelveticaNeue", size: 18.0)
label.textColor = UIColor(red: 0.89, green: 0.38, blue: 0.0, alpha: 1.0)
label.textAlignment = .center
status.addSubview(label)
此方法的一部分添加了存储在类变量状态中的隐藏图像视图。 然后代码创建一个文本标签,并将其添加为状态的子视图。
您将使用status向用户显示进度消息。 消息来自messages数组.
将以下方法添加到ViewController:
func showMessage(index: Int) {
label.text = messages[index]
UIView.transition(with: status, duration: 0.33,
options: [.curveEaseOut, .transitionCurlDown],
animations: {
self.status.isHidden = false
},
completion: {_ in
//transition completion
} )
}
此方法采用一个名为index的参数,您可以使用该参数将label的值设置为基于索引的message内容。
接下来,您可以调用transition(with:duration:options:animations: completion:)来为视图设置动画。 要在转换时显示banner,请在动画块中将isHidden设置为false。
上面还有另一个新的动画选项:.transitionCurlDown。 此过渡动画视图就像在合法的垫上翻下一张纸,看起来如下所示:
是时候锻炼你的新showMessage(index :)方法了。 在login()中找到以下代码块:
animations: {
self.loginButton.bounds.size.width += 80.0
}, completion: nil)
这是您现有的动画块,当用户点击它时,登录按钮会弹回。 你将为这个调用showMessage(index :)的动画添加一些新东西 - 一个完成闭包。
用以下闭包表达式替换completion的nil参数值:
completion: { _ in
self.showMessage(index: 0)
}
闭包采用一个Bool参数,该参数告诉您动画是否成功完成或在运行完成之前被取消。
注意:上面的完成闭包有一个完成的参数。 因为你不关心它是否完成,Swift允许你通过在其位置放置“_”来跳过绑定参数。
在闭包内部,您只需调用带有索引0的showMessage来显示来自messages数组的第一条消息。
构建并运行您的项目; 点击登录按钮,您将看到状态标题显示您的第一个进度消息。
你有没有看到横幅如何像一张纸一样卷曲? 这是一种非常好的方式来引起对通常只显示为静态文本标签的消息的关注。
注意:你的一些动画似乎运行得非常快,不是吗? 有时确保动画恰好在正确的位置以正确的顺序发生是很棘手的。 或者也许你只是希望事情发生得更慢,这样你才能体会到这种效果!
要在不更改代码的情况下减慢应用程序中的所有动画,请从iPhone模拟器菜单中选择最前面的Debug/Toggle Slow Animations”。 现在点击应用程序中的“登录”按钮,以生动的慢动作欣赏动画和过渡!
对于下一个动画,您首先需要保存banner的初始位置,以便将下一个banner放在正确的位置。
创建一个用于储存status初始位置的全局变量
var statusPositon = CGPoint.zero
将以下代码添加到viewDidLoad()的末尾,以将banner的初始位置保存到名为statusPosition的属性:
statusPosition = status.center
现在,您可以开始设计视图动画和过渡的混合体。 添加以下方法以通过标准动画从屏幕中删除状态消息:
func removeMessage(index: Int) {
UIView.animate(withDuration: 0.33, delay: 0.0, options: [],
animations: {
self.status.center.x += self.view.frame.size.width
},
completion: { _ in
self.status.isHidden = true
self.status.center = self.statusPosition
self.showMessage(index: index+1)
}
) }
在以上代码中,你使用了animate(withDuration:delay:options:animations:completion:)移动status到屏幕之外
当动画在完成闭包中完成时,您将status移回其原始位置并隐藏它。 最后再次调用showMessage,但这次你传递下一条要显示的消息的索引。
将标准动画与过渡相结合非常简单:只需调用适当的API,UIKit就可以在后台调用相应的Core Animation位。
现在,您需要完成showMessage和removeMessage之间的调用链,以模拟真实的身份验证过程。
将以下代码添加在import UIKit下:
func delay(_ seconds: Double, completion: @escaping ()->Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: completion)
}
找到showMessage(index :)并在completion中使用以下代码替换并注释//transition completion:
completion: { _ in
//transition completion
delay(2.0) {
if index < self.messages.count-1 {
self.removeMessage(index: index)
} else {
}
}
}
转换完成后,等待2.0秒并检查是否有剩余消息。 如果是这样,请通过removeMessage(index :)删除当前消息。 然后在removeMessage(index :)的完成块中调用showMessage(index :)以按顺序显示下一条消息。
再次构建并运行您的项目; 享受生成的动画序列,更新身份验证进度消息,如下所示:
过渡是动画知识的一个小但重要的子集,可以保存在您的图形工具箱中,因为它们是在UIKit中创建3D样式动画的唯一方法。
如果您期待学习更精细的3D效果,您将有机会在第VI部分“3D动画”中进行操作,该部分详细讨论了核心动画和3D图层转换。
在继续下一部分之前,请尝试本章中的挑战。 既然你在最后三章中学到了很多关于动画的知识,那么一个挑战是不够的 - 我给了你三个!
这些挑战使您有机会在Bahama Air登录屏幕上完成开发 - 并且还可以接受您的第一次超级挑战.
关键点(Key Points)
- Apple提供了一组预定义的动画,称为过渡,可用于处理应用UI状态的特殊更改。
- 转换的目标是在视图层次结构中添加,删除和替换视图。
- 在设计动画时,可以启用模拟器中Debug ▸ Toggle Slow Animations切换慢动画,以便能够以慢动作观察它们。
挑战(Challenges)
挑战1:选择您最喜欢的过渡
到目前为止,您只看到了一个内置的过渡动画。 你不是很想看看其他人的样子吗?
在此挑战中,您可以尝试所有其他可用的过渡动画,并使用您喜欢的动画制作进度消息banner。
打开ViewController并在showMessage(index :)中找到指定过渡动画的行.transitionCurlDown:
UIView.transitionWithView(status, duration: 0.33, options:
[.curveEaseOut, .transitionCurlDown], animations: ...
将.transitionCurlDown替换为可用的任何其他过渡动画,然后构建并运行项目以查看它们的外观。 以下是可用转换的列表:
.transitionFlipFromLeft
.transitionFlipFromRight
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
.transitionFlipFromTop
.transitionFlipFromBottom
您认为哪一个最适合此屏幕上的其他动画?
如果您没有喜欢的话,请尝试我的最爱转换:.transitionFlipFromBottom。 我认为它与banner图形非常吻合.
挑战2:将表单重置为初始状态
对于此挑战,您可以通过撤消点击“登录”按钮后运行的所有动画,将表单重置为初始状态。 这样,如果登录失败,用户会在第二次点击“登录”按钮时再次看到所有动画。
以下列出了完成此挑战所需的一般步骤:
- 创建一个新的空方法resetForm(),并从占位符注释////reset form lives所在的代码中调用它。
- 在resetForm()中,使用transition(with:duration:options:animations:completion :)将status的可见性设置为hidden,将center设置为self.statusPosition。 这应该将banner重置为其初始状态。 使用0.2秒的持续时间进行转换。
- 如果隐藏你的banner的过渡使用与显示banner的动画完全相反的动画,那将是很好的。 例如,如果您通过.transitionCurlDown显示banner,则使用.transitionCurlUp隐藏它。 .transitionFlipFromBottom的反向是.transitionFlipFromTop …等等。
- 接下来,在resetForm()中添加调用animate(withDuration:delay:options: animations:completion:)。 在动画闭包块内进行以下调整:
- 将self.spinner - “登录”按钮内的活动指示器移动到其原始位置(-20.0,16.0)。
- 将self.spinner的alpha属性设置为0.0以隐藏它。
- 将“登录”按钮的背景色调回原始值:UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0).
- 继续重置您对“登录”按钮所做的所有更改并减少bounds.size.width属性80.0 points。
- 最后,将按钮移回密码字段下的原始位置将center.y减少60.0 points。
在showMessage中调用resetForm()
func showMessage(index:Int){
label.text = message[index]
UIView.transition(with: status, duration: 0.33, options: [.curveEaseOut,.transitionFlipFromBottom], animations: {
self.status.isHidden = false
}, completion: {_ in
delay(2.0, completion: {
if index < self.message.count - 1{
self.removeMessage(index: index)
}else{
self.resetForm()
}
})
}
)
}
如果您在身份验证过程中精确地反转了所有动画,则在显示所有身份验证消息后,屏幕应如下所示:
resetForm()代码如下:
func resetForm(){
UIView.transition(with: loginButton, duration: 0.2, options: [], animations: {
self.status.isHidden = true
self.status.center = self.statusPosition
}, completion: nil)
UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {
self.spinner.center = CGPoint(x: -20.0, y: 16.0)
self.spinner.alpha = 0
self.loginButton.backgroundColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
self.loginButton.bounds.size.width -= 80
self.loginButton.center.y -= 60
}, completion: nil)
}
做得好! 现在为本章的最后挑战…
挑战3:在背景中为云设置动画
如果背景中的那些云在屏幕上缓慢移动并从另一侧重新出现,那不是很酷吗?
是的,它会非常酷 - 这就是你的挑战!
四个云图像视图已连接到ViewController中的四个插座,因此您可以轻松上手。 您可以尝试使用新发现的过渡动画知识使云自行移动,或者您可以按照以下方法:
- 使用签名animateCloud(cloud:UIImageView)创建一个新方法,并在其中添加您的代码。
- 首先,计算平均云速。假设云应该在大约60.0秒内穿过屏幕的整个长度。创建常量cloudSpeed并将其设置为60.0 / view.frame.size.width。
- 接下来,计算动画将云移动到屏幕右侧的持续时间。请记住,云不是从屏幕的左边缘开始,而是从随机点开始。您可以通过获取云需要遵循的路径长度并将结果乘以平均速度来计算正确的持续时间:(view.frame.size.width - cloud.frame.origin.x)* cloudSpeed。
- 然后使用上面计算的持续时间调用animate(withDuration:delay:options:animation:completion : )。 您需要从中创建一个TimeInterval实例,因为编译器不会为您确定正确的类型:TimeInterval(duration)。 对于options参数,请使用.curveLinear; 这是你很少使用动画而没有缓和的少数几次之一。 云在背景中自然相当远,因此它们的运动应该看起来非常均匀。
- 在动画闭包表达式中,将cloud的frame.origin.x属性设置为self.view.frame.size.width。 这会将云移动到屏幕区域之外。
- 在闭包completon内部,将云移动到屏幕的相对边缘之外,从当前位置开始。 不要忘记使用“_”跳过closure参数,如本章前面所述。请将云位置属性frame.origin.x设置为-cloud.frame.size.width。
- 仍然在闭包completion中工作,添加对animateCloud()的调用,以便云在屏幕上重新设置动画。
- 最后,将以下代码添加到viewDidAppear()的末尾,以启动所有四个云的动画:
animateCloud(cloud1)
animateCloud(cloud2)
animateCloud(cloud3)
animateCloud(cloud4)
这应该使所有四个云缓慢地穿过屏幕,以创建一个漂亮,不引人注目的效果。
如果您完成了本章的挑战,恭喜! 他们很难了!
在过去的几章中有很多要消化的信息,但是你采用了一种僵硬的静态登录表单,并将其变成了一个引人注目且有趣的用户体验:
animateCloud()代码如下:
func animateCloud(_ cloud:UIImageView){
let cloudSpeed = 60.0/view.frame.size.width
let duration = (view.frame.size.width - cloud.frame.origin.x)*cloudSpeed
UIView.animate(withDuration: TimeInterval(duration), delay: 0, options: [.curveLinear], animations: {
cloud.frame.origin.x = self.view.frame.size.width
}) { _ in
cloud.frame.origin.x = -cloud.frame.size.width
self.animateCloud(cloud)
}
}
现在是时候从新材料中休息一下,并将所有观看动画知识付诸实践! 在下一章中,您将使用各种各样的实用知识来为Bahama Air应用程序添加一些认真的润色。