在 Swift 中使用 iOS 视图动画和过渡





0/5 (0投票)
在 Swift 中使用 iOS 视图动画和过渡
如果您使用 Apple 设备,您可能已经看到过第三方应用程序或 Apple 自带应用程序中的许多动画。如果您曾希望知道如何做到这一点,但又觉得它太复杂了,那么您的愿望即将实现。跟我一起学习,您将发现,在 iOS 中创建动画不仅不复杂,而且很有趣。
动画能吸引用户的注意力,让他们专注于屏幕上的重要内容。动画还能突出屏幕上的变化,并帮助用户学习导航您的应用程序。但说实话,大家都喜欢动画。它们就是那么酷。
您需要的工具
要跟随本教程,假设您是一名 iOS 开发人员,希望探索动画的世界,您将需要一些前提条件:
- 一台运行最新 macOS X Sierra 或更高版本操作系统的 Mac。
- Xcode 8 或更高版本。
- Swift 4 语言和 Auto Layout 的基本知识。
如果您不具备这些条件,可以继续获取它们,我在这里等着...
入门
现在您已经准备好了所需的一切,我已经在小项目中设置好了供您使用。您可以 在这里 下载,或者如果您更喜欢查看 Git 仓库,也可以这样做。
下载或克隆后,请打开项目文件。您应该会看到类似这样的文件结构:
打开 Main.storyboard
。您会看到一个黑金主题的视图控制器。
它恰如其分地命名为 **Next Level Designs**,包含一个标签、两个 UIViews
:一个金色,一个绿色,以及底部的“Transition”按钮。金色的 UIView
叠加在绿色的上面;这就是为什么您目前看不到它的原因。
您可以运行项目,看看设计在实际运行中的效果。我们将使用这个视图控制器来演示视图过渡。
让我们开始吧。
视图过渡
正如您可能猜到的,我们将要过渡的是 UIViews
。我们的目标很简单:当我们点击 Transition
按钮时,我们希望在金色 UIView
和绿色 UIView
之间发生过渡,再次点击时则反之。
但在我们能够做到这一点之前,我们必须将一些 outlet 连接到自定义类。打开 Main.storyboard
,选择 **Next Level View Controller**,然后点击 Xcode 右上角的 Assistant Editor(那个看起来像两个重叠圆圈的图标)。
我们首先要连接的是我们的两个 UIViews
,由于一个叠加在另一个上面,连接它们的最简单方法是从左侧的 Document Outline 进行。
从那里,您可以选择 **Option + 拖动** 或 **右键单击拖动** 到 Xcode 文件中,就在 viewDidLoad()
方法的上方。
将金色视图命名为 goldenView
,然后点击“Connect”。
您可以通过查看右侧 Attributes Inspector 中的背景颜色来确认您正在连接的 UIView
。
对 greenView
执行相同的操作,最后在 viewDidLoad()
方法之后连接 Transition
按钮。
确保它是一个 Action 且类型为 UIButton
。
现在一切就绪,准备好向苹果公司那些热心人士寻求我们驻场过渡 API 的帮助吧。
过渡 API
如果您熟悉 UIKit Animations API,Transition API 与之非常相似,并且实现起来相当简单。但是,有一些区别,我们将一一介绍。
首先要知道的是,Transition API 有两种实现方式,表面上看起来非常相似,因为它们做的事情相同。然而,它们的运作方式略有不同。
在我们的 transition()
action 方法中,如果您开始输入 UIView.transition
,XCode 的自动完成功能会给您一些选项:
我们对前两个选项感兴趣,为了更好地解释它们的工作原理,我们将同时使用它们来过渡我们的 UIViews
。
UIView.transition(with: …)
让我们从第二个选项开始,即带有 with:
参数的那个。它是比较直接的。
视图过渡是预定义的,这意味着它们在一定程度上受到限制,但非常强大且易于实现。要实现过渡,您只需做一件事:将一个过渡触发器与一个预定义的过渡配对。
自然,您接下来的问题是:“这很好,但是什么是过渡触发器?什么是预定义的过渡?我该如何做?”
请耐心等待,一切都会适时揭晓。让我们先从过渡触发器开始。
有三个触发器应该会引起视图过渡:
isHidden
- 这个触发视图的isHidden
属性,使其从视图中消失。addSubview()
- 这个方法将一个子视图添加到视图的视图层级结构中。removeFromSuperview()
- 这个方法的作用相反,将一个视图从其父视图的视图层级结构中移除。
触发器放在 animations:
闭包中。
至于过渡,有七种:
.transitionFlipFromLeft
.transitionFlipFromRight
.transitionFlipFromTop
.transitionFlipFromBottom
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
这些过渡效果正如其名,我们稍后会看到。它们放在 options:
参数中。
现在您已经跟上进度了,让我们开始过渡吧。在您的 UIView.transition(with: …)
实现中,将 with:
参数设置为 goldenView
,因为它在最上面,将 duration:
设置为 0.5,在 options:
中输入 [.transitionFlipFromRight]
,然后在 animations:
闭包中,将 goldenView
的 isHidden
属性设置为 true
。
您的实现应该如下所示:
UIView.transition(with: goldenView, duration: 0.5, options: [.transitionFlipFromRight], animations: { self.goldenView.isHidden = true }, completion: nil)
构建并运行项目,点击 Transition
按钮,看看会发生什么。
恭喜您完成了第一次视图过渡。给自己一个击掌!
然而,我们的过渡项目远未完成。过渡只发生一次,这并非我们想要的效果。我们需要找到一种方法使其来回过渡。
在同一个文件中,在我们的 UIViews
outlets 的正下方,定义一个私有常量,命名为 isFlipped
,并将其初始化为 false
。
private var isFlipped: Bool = false
接下来,告诉我们的过渡方法,按钮被点击时应该翻转哪个 UIView
。
我们需要翻转的卡片是 goldenView
(当 isFlipped
为 true
时)和 greenView
(当 isFlipped
为 false
时)。
在我们的按钮 action 方法中,添加一个基于 isFlipped
属性的三元运算符,该运算符决定要翻转的卡片是 goldenView
还是 greenView
。
@IBAction func transition(_ sender: UIButton) { isFlipped = !isFlipped
然而,我们目前的 isFlipped
属性是静态的,这意味着它将始终为 false
。所以我们需要在每次点击按钮时更改 isFlipped
的值。
要做到这一点,在 cardToFlip
的声明上方添加以下内容:
isFlipped = !isFlipped
最后一件事,在 UIView.transition(with: …)
方法中,将 goldenView
的所有实例替换为 cardToFlip
。
整个方法现在应该如下所示:
@IBAction func transition(_ sender: UIButton) { isFlipped = !isFlipped let cardToFlip = isFlipped ? goldenView : greenView UIView.transition(with: cardToFlip!, duration: 0.5, options: [.transitionFlipFromRight], animations: { cardToFlip!.isHidden = true }, completion: nil) }
构建并运行项目。现在注意卡片可以相互交替过渡。
哦,那是什么?卡片正确翻转然后消失了?当然会!因为我们告诉 cardToFlip
隐藏了,但没有告诉它从隐藏状态中出来。
要解决这个问题,我们必须使用 completion 闭包,并将 cardToFlip
的 isHidden
属性重新设置为 false
。您的 completion 闭包现在应该如下所示:
completion: { _ in cardToFlip?.isHidden = false })
再次构建并运行项目。
您应该遇到了另一个问题,这次是 goldenView
过渡出视图,然后在一段时间后尴尬地重新出现。我知道您可能在想什么,不,我没有和您开玩笑。这实际上是一个关于视图层级的教学时刻。
我不会深入探讨视图层级,因为这不是本文的主题。但基本上,视图也可以充当其他视图的容器,从而创建视图层级。
在我们的特定案例中,我们的两个 UIViews
包含在视图控制器的根视图中,因此是它的子视图。goldenView
最初放置在 greenView
的前面,并且除非更改顺序,否则将保持在那里。
幸运的是,有一个内置方法可以帮助我们。
首先,我们必须捕获我们的 bottomCard
。所以,在 cardToFlip
的三元运算符下方,为 bottomCard
添加另一个。
let bottomCard = isFlipped ? greenView : goldenView
接下来,在 completion 闭包中,在告诉 cardToFlip
停止隐藏的行上方,添加以下代码行,将 bottomCard
提到前面:
self.view.bringSubview(toFront: bottomCard!)
您的整个 action 方法现在应该如下所示:
@IBAction func transition(_ sender: UIButton) { isFlipped = !isFlipped let cardToFlip = isFlipped ? goldenView : greenView let bottomCard = isFlipped ? greenView : goldenView UIView.transition(with: cardToFlip!, duration: 0.5, options: [.transitionFlipFromRight], animations: { cardToFlip?.isHidden = true }, completion: { _ in self.view.bringSubview(toFront: bottomCard!) cardToFlip?.isHidden = false }) }
现在再次构建并运行项目。我保证这次它会奏效。😄
看!这就是我们想要的效果。
还有一件事:当一个过渡是由添加或移除视图到层级结构触发时,**即** addSubview()
和 removeFromSuperview()
**,您需要在 with:
参数中使用视图的父视图。**
当过渡由 isHidden
触发时,视图充当自己的容器视图。
现在让我们看看过渡 API 的另一个变体。
UIView.transition(from: …)
这个变体,表面上看起来更简单。然而,正如您稍后会发现的,有一些底层功能在发生。
我们将对我们的 UIViews
应用与之前相同的过渡,所以请注释掉之前的 UIView.transition(with: …)
实现。
和之前一样,在按钮的 action 方法中,在 bottomCard
的定义正下方,开始输入 UIView.transition
,并从 Xcode 自动完成中选择第一个变体——带有 from:
参数的那个。
我们希望从 cardToFlip
过渡到 bottomCard
。所以我们将 cardToFlip
用于 from:
参数,将 bottomCard
用于 to:
参数。让我们将 duration:
参数设置为相同的 0.5 秒,并使用 [.transitionCurlUp]
作为 options:
参数。暂时将 completion:
设置为 nil
。
您的过渡方法现在应该如下所示:
UIView.transition(from: cardToFlip!, to: bottomCard!, duration: 0.5, options: [.transitionCurlUp], completion: nil)
生成并运行项目。
第一次过渡如预期发生,但当您第二次点击按钮时,编译器会给出 **⚠ 致命错误**。
您可能已经注意到,这个变体没有像另一个那样拥有 animations:
闭包,而我们需要 animations:
闭包来触发过渡。好吧,正如我之前提到的,这个变体在后台有一些自动处理的过程。
如果您查看底部的变量视图,您会注意到 bottomCard
(在本例中是 goldenView
)为 nil
。
发生的事情是:自动过渡触发器将 goldenView
从视图层级结构中移除了。
在苹果官方 文档 中,他们指出,在此变体中,fromView:
在过渡过程中会从其父视图中移除。
好吧,这都可以,但我们不能就这样忍受这个 **致命错误**。我们必须以某种方式修复它,有两条途径可以做到。
一种方法是在 completion:
闭包中将 cardToFlip
添加回其父视图的视图层级结构,然后像之前一样将 bottomCard
提到前面。但您已经学会了如何做,所以让我们来探索另一个,**更快捷**(双关语😏)的选项。
在 options:
参数集中,添加另一个名为 .showHideTransitionViews
的选项。它的作用只是隐藏 fromView
,而不是将其从视图层级结构中移除。
您的过渡方法现在应该如下所示:
UIView.transition(from: cardToFlip!, to: bottomCard!, duration: 0.5, options: [.transitionCurlUp, .showHideTransitionViews], completion: nil)
再次构建并运行项目。
我们的过渡现在工作正常,但是,还有一个小问题。整个页面而不是视图会向上翻卷,而我们肯定不希望这样。在此过渡方法的变体中,过渡仅发生在父视图上,并且不幸的是,无法使过渡仅发生在子视图上。这就是自动化带来的局限性。
唯一的变通方法是将我们的两个 UIViews
嵌入另一个大小和位置相同的 UIView
中,使其充当它们的父视图,而不是主根视图。
要做到这一点,打开 Main.storyboard
,然后在文档大纲中,通过 **Command + 单击** 来选中两个 UIViews
。然后转到 **Editor/Embed In/View**。
您现在应该看到一个比其他两个稍大的白色视图。您应该注意到左侧的文档大纲中,我们的两个 UIViews
现在是新 UIView
的子视图。
调整新 UIView
的大小可能会很棘手,所以我将引导您完成。
- 首先,抓住白色
UIView
的左边缘,并将其拉到主根视图的边界内。 - 接下来,选择
goldenView
和greenView
,并将它们同时移动到白色UIView
的左上角,并使它们与白色UIView
的顶部和左边缘完美对齐。 - 现在,抓住白色
UIView
的右下角,并将其调整大小以适应goldenView
和greenView
的尺寸。 - 最后,从文档大纲中选择白色
UIView
,然后同时将所有三个UIViews
移动到goldenView
和greenView
之前的位置。
最后一次构建并运行项目,并欣赏您工作的成果。
一如既往,现在由您来尝试和探索其他过渡选项,看看您最喜欢哪一个。