SwiftUI动画之使用 navigationTransition(.zoom) 实现 Hero 动画

使用navigationTransition(.zoom(sourceID:in:))在 SwiftUI 中实现 Hero 式放大过渡

SwiftUI iOS 17 带来了新的导航过渡系统。本文将带你学习如何使用 navigationTransition(.zoom(sourceID:in:)) 实现类似 Hero 动画的平滑放大效果。


✨ 简介

在 UIKit 时代,想要实现一个"列表 → 详情页"的放大过渡动画,往往需要复杂的自定义转场或者第三方库 Hero。而从 iOS 17 开始,SwiftUI 提供了新的导航过渡 API,使得这一切都能以极少的代码实现。

navigationTransition(.zoom(sourceID:in:)) 是 Apple 新增的 API,它允许我们在两个导航页面间创建共享元素(Shared Element)动画。源视图与目标视图使用相同的 sourceID,系统就能自动识别并生成缩放过渡。


🧩 实现思路

  1. 定义一个 @Namespace 来管理共享动画空间。

  2. 在源视图(比如卡片)与目标视图(详情页)上使用相同的 id。

  3. 通过 .navigationTransition(.zoom(sourceID:in:)) 声明使用 zoom 过渡。

系统会自动在两个页面间平滑地缩放、移动这两个匹配的视图,形成 Hero 式的过渡体验。


💻 完整示例代码

less 复制代码
import SwiftUI

struct ZoomHeroExample: View {
    @Namespace private var ns
    @State private var selected: Item? = nil
    
    let items: [Item] = [
        Item(id: "1", color: .pink, title: "粉红"),
        Item(id: "2", color: .blue, title: "湛蓝"),
        Item(id: "3", color: .orange, title: "橙色")
    ]
    
    var body: some View {
        
        NavigationStack {
            ScrollView {
                LazyVGrid(columns: [.init(.adaptive(minimum: 120))], spacing: 12) {
                    ForEach(items) { item in
                        RoundedRectangle(cornerRadius: 12)
                            .fill(item.color)
                            .frame(height: 140)
                            .overlay(Text(item.title).foregroundColor(.white).bold())
                            .matchedTransitionSource(id: "card-" + item.id, in: ns)
                            .onTapGesture { selected = item }
                    }
                }
                .padding()
            }
            .navigationDestination(item: $selected) { item in
                // 添加 zoom 过渡
                DetailView(item: item, namespace: ns)
                    .navigationTransition(.zoom(sourceID: "card-" + item.id, in: ns))
            }
            .navigationTitle("颜色卡片")
        }
    }
}

struct DetailView: View {
    let item: Item
    let namespace: Namespace.ID
    
    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 24)
                .fill(item.color)
                .frame(height: 420)
                .padding()
            
            Text(item.title)
                .font(.largeTitle)
                .bold()
            
            Spacer()
        }
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct Item: Identifiable, Hashable {
    let id: String
    let color: Color
    let title: String
}


#Preview {
    ZoomHeroExample()
}

🎬 动画效果说明

当用户点击某个卡片:

  1. 源视图与目标视图通过相同的 sourceID 匹配。

  2. 系统自动计算两者在坐标空间中的差异。

  3. SwiftUI 执行一个 .zoom 类型的放大过渡动画,使卡片平滑地扩展到详情页。

这与 Hero 库的核心概念完全一致,但现在由 SwiftUI 原生支持。


⚙️ 常见问题

❓ 为什么动画没生效?

  • 确认源视图和目标视图的 id 一致。

  • 确保使用的是 NavigationStack 而不是旧的 NavigationView。

  • 确认系统版本 ≥ iOS 17。

⚠️ 视图闪烁或跳动?

  • 避免动态尺寸变化过大的布局。
  • 给匹配元素一个固定的 .frame() 可增强过渡的稳定性。

🔍 与matchedGeometryEffect的对比

特性 matchedGeometryEffect navigationTransition(.zoom)
匹配方式 Namespace + ID sourceID + Namespace
场景 任意动画、自定义匹配 导航过渡专用
代码复杂度 较高 极简
稳定性 手动控制 系统托管

简言之:在导航场景下优先使用 navigationTransition,其它复杂动画仍可用 matchedGeometryEffect。


💡 延伸思考

如果你想自定义更多转场样式,比如滑动、淡入淡出,可以尝试:

less 复制代码
.navigationTransition(.slide(sourceID: "card-" + $0.id, in: ns))

或:

less 复制代码
.navigationTransition(.zoom(sourceID: "card-" + $0.id, in: ns).combined(with: .opacity))

SwiftUI 让多个过渡组合成为可能,且依旧保持声明式风格。


🧭 总结

通过 navigationTransition(.zoom(sourceID:in:)),我们可以在 SwiftUI 中轻松实现 Hero 式放大动画。它不仅简化了过渡代码,还 seamlessly 与 NavigationStack 集成。

一句话总结:

从此以后,Hero 动画在 SwiftUI 中,不再需要 Hero。


参考链接:

相关推荐
songgeb17 小时前
YYImage Bug:一行之差,正常 JPEG 如何在 iOS 被读成条纹
ios·swift
茶底世界之下1 天前
阴天花摊不必变暖:用 Awaking 留住花色与湿街
ios·app·产品
Fatbobman(东坡肘子)2 天前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
开发语言·人工智能·redis·ios·swiftui·iphone·swift
茶底世界之下2 天前
斜光下的旧自行车:用 Awaking 留一版复古胶片色
ios·app·产品
2501_916008892 天前
iPhone 设备信息查看,用 KeyMob 看型号、序列号、电池健康和 UDID
android·ios·小程序·https·uni-app·iphone·webview
茶底世界之下3 天前
iPhone Duo 适配实战,Awaking 踩过的 8 个坑
ios·swiftui·swift
跨境数据猎手3 天前
HTTP 响应体编码与压缩:识别、解码
网络协议·http·ios
也非非也3 天前
iPhone 中国区 AI 落地:Apple 与阿里千问合作的技术观察
大数据·人工智能·ios·agent·iphone·智子
茶底世界之下3 天前
同一片泳池,清透还是奶油胶片?用 Awaking 做两版成片
ios