白玉微瑕:闲谈 SwiftUI 过渡(Transition)动画的“口是心非”(上)

12 阅读2分钟

在这里插入图片描述

概述

秃头小码农们都知道,SwiftUI 不仅仅是一个静态 UI 构建框架那么简单,辅以海量默认或自定义的动画和过渡(Transition)特效,我们可以将 App 界面的绚丽升华到极致。

在这里插入图片描述

不过,目前 SwiftUI 中的过渡(Transition)动画在某些“敏感”场景中会有让人意想不到的效果,我们如何随机应变回归本源呢?本篇由此应运而生。

在本篇博文中,您将学到如下内容:

  1. 茫然若失:消失的 Transition 动画
  2. 什么是 Transition 过渡动画?

本文示例代码测试环境:macOS 15.2 + Xcode 16.1

百闻不如一见,那小伙伴们还等什么呢?让我们马上开始 Transition 动画除虫大冒险吧!

Let‘s go!!;)

1. 茫然若失:消失的 Transition 动画

这是一段非常简单的 SwiftUI 代码,我们在用户选中某个 WorryObject 对象时在对应视图的右上角显露出一个绿色的圆形标志:

@FetchRequest(sortDescriptors: [.init(keyPath: \WorryObject.name, ascending: false)]) var allWorryObjects: FetchedResults<WorryObject>
@State var selectingWorryObjectID: UUID?
@Namespace private var ns

DisclosureGroup("担忧对象") {
    LazyVGrid(columns: [GridItem](repeating: .init(), count: 3)) {
        ForEach(allWorryObjects) { wObject in
            VStack {
                Text(wObject.name ?? "")
                    .font(.title3.bold())
                
                Text(wObject.level.title)
                    .font(.caption2)
                    .foregroundStyle(.gray)
                    .padding(.vertical, 8)
                
                Text(wObject.type.title)
            }
            .contentShape(Rectangle())
            .padding()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .overlay {
                RoundedRectangle(cornerRadius: 10)
                    .stroke(.gray, lineWidth: selectingWorryObjectID == wObject.oid ? 3.0 : 1.0)
            }
            .overlay(alignment: .topTrailing) {
                if selectingWorryObjectID == wObject.oid {
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundStyle(.green.gradient)
                        .font(.title3.bold())
                        .transition(.scale)
                        .matchedGeometryEffect(id: 1, in: ns, isSource: selectingWorryObjectID == wObject.oid)
                }
            }
            .onTapGesture {
                withAnimation(.bouncy) {
                    if selectingWorryObjectID == wObject.oid {
                        selectingWorryObjectID = nil
                    } else {
                        selectingWorryObjectID = wObject.oid
                    }
                }
            }
            .opacity(selectingWorryObjectID == wObject.oid ? 1.0 : 0.33)
        }
    }
    .listRowSeparator(.hidden)
}

从上面代码可以清楚的看到,我们在绿色选中标志上应用了缩放(scale)过渡动画(transition)效果。

不过,如果在 Xcode 预览中仔细观察运行结果,我们会发现绿色选中标志在出现时没有任何动画效果(突然显现),只有在消失时才有一丝的动态点缀:

在这里插入图片描述

这是怎么回事呢?难道是 SwiftUI 过渡动画偶尔“发挥失常”?

2. 什么是 Transition 过渡动画?

SwiftUI 过渡(或称为转场)Transition 动画是普通动画的一种特殊形式,它们被专门用于描述视图是如何“从无到有或从有到无”的。

在这里插入图片描述 在这里插入图片描述

我们来看一段轻车简从的 SwiftUI 代码:

struct ContentView: View {
    
    @State var magic = false

    var body: some View {
        NavigationStack {
            VStack {
                if magic {
                    Circle()
                        .foregroundStyle(.green.gradient)
                        .frame(width: 200, height: 200)
                        .transition(.scale)
                }
            }
            .toolbar {
                Button("Magic") {
                    withAnimation(.bouncy) {
                        magic.toggle()
                    }
                }
            }
        }
    }
}

上面这段简单的不能再简单的代码很好的诠释了 Transition 动画的特性:我们根据 magic 状态将一枚绿色的圆形从 SwiftUI 渲染树中插入或删除,并同时在它之上应用了缩放过渡动画。

在这里插入图片描述

在下篇博文中,我们将会继续讨论 SwiftUI 过渡动画的“阿格琉斯之踵”以及规避方法,敬请期待吧!

总结

在本篇博文中,我们简单介绍了什么是 SwiftUI 中的过渡(Transition)动画,以及它们在某些场景下会出现的一些小“故障”的表现。

感谢观赏,我们下篇再会吧!8-)