概述
秃头小码农们都知道,SwiftUI 不仅仅是一个静态 UI 构建框架那么简单,辅以海量默认或自定义的动画和过渡(Transition)特效,我们可以将 App 界面的绚丽升华到极致。
不过,目前 SwiftUI 中的过渡(Transition)动画在某些“敏感”场景中会有让人意想不到的效果,我们如何随机应变回归本源呢?本篇由此应运而生。
在本篇博文中,您将学到如下内容:
- Transition 动画之“怪癖”
- 暗度陈仓:用动画(Animation)代替过渡(Transition)
本文示例代码测试环境:macOS 15.2 + Xcode 16.1
百闻不如一见,那小伙伴们还等什么呢?让我们马上开始 Transition 动画除虫大冒险吧!
Let‘s go!!;)
3. Transition 动画之“怪癖”
虽然 Transition 动画在大多数情况下表现的都十分惊艳,但“金无足赤,人无完人”,Transition 动画在某些场景下也会“选择性失灵”。
其中一种场景就是:若视图同时包裹在 NavigationStack 和 Form(或 List)容器内,则视图的插入 Transition 过渡动画会消失不见,变为“鸟迹虫丝”。
在下面的代码中,我们测试的两枚绿色圆形分别放在了不同的容器中:
- 顶部的圆形放在 NavigationStack 和 List 中;
- 底部的圆形只放在了 NavigationStack 中;
struct ContentView: View {
@State var magic = false
var body: some View {
NavigationStack {
VStack(alignment: .leading) {
List {
Section("同时嵌入在 NavigationStack 或 List 中") {
VStack {
if magic {
Circle()
.foregroundStyle(.green)
.frame(width: 100, height: 100)
.transition(.scale)
}
Spacer()
Text("没有插入,只有消失时的缩放过渡动画")
}
.frame(height: 200)
}
}
.listStyle(.plain)
Divider()
Section("只嵌入在 NavigationStack 中") {
VStack {
if magic {
Circle()
.foregroundStyle(.green)
.frame(width: 100, height: 100)
.transition(.scale)
}
Spacer()
Text("插入和消失时皆有缩放过渡动画")
}
.frame(height: 200)
}
Button("Magic") {
withAnimation(.bouncy) {
magic.toggle()
}
}
}
.padding()
.navigationTitle("过渡动画“怪癖”演示")
}
}
}
运行结果如下所示:
可以看到:顶部圆形没有插入过渡动画,而底部圆形的过渡动画完美无缺。
注意,上面代码中两个圆形视图上的过渡动画代码都是完全一致的,不存在特殊对待的情况。
4. 暗度陈仓:用动画(Animation)代替过渡(Transition)
对于上面过渡(Transition)动画的这种“怪癖”,我们有一些解决办法来绕过它。不过,它们大多比较复杂,那么有没有简单的方法呢?
答案是肯定的!
其实,SwiftUI 中的普通动画与过渡动画是非常类似的,从本质上来说:在播放动画时前者会保持视图“稳定”,而后者会导致视图被插入或删除。
有了这一思路,我们就可以轻松的将过渡动画转换为普通的动画了:
Section("同时嵌入在 NavigationStack 或 List 中") {
VStack {
Circle()
.foregroundStyle(.green)
.frame(width: 100, height: 100)
.scaleEffect(magic ? .init(width: 1, height: 1) : .zero)
Spacer()
Text("利用普通动画解决过渡动画丢失的问题")
}
.frame(height: 200)
}
从上面的代码可以看出,我们将原先绿色 Circle 上的过渡动画转换成了在视图尺寸上缩放的普通动画效果:
同样,对于博文开头 WorryObject 选择过渡动画缺失的情况,我们也可以如法炮制:
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green.gradient)
.font(.title3.bold())
.scaleEffect(selectingWorryObjectID == wObject.oid ? .init(width: 1, height: 1) : .zero)
.matchedGeometryEffect(id: 1, in: ns, isSource: selectingWorryObjectID == wObject.oid)
最后运行一下,在 Xcode 预览中欣赏一番我们的最终成果吧:
现在,对于那些 SwiftUI 中过渡动画失灵又无计可施的撸码场景我们也可以轻松应对了,棒棒哒!💯
总结
在本篇博文中,我们进一步讨论了 SwiftUI 过渡动画在什么场景下可能会掉链子,并用一招将其彻底驯服。
感谢观赏,再会啦!8-)