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。


参考链接:

相关推荐
加油乐14 小时前
解决 iOS 端输入框聚焦时页面上移问题
前端·javascript·ios
电话交换机IPPBX-3CX20 小时前
电话交换机软件 3CX iOS 应用 V5.4 Beta 更新
ios·软件更新·ip pbx·电话交换机
初遇你时动了情1 天前
uniapp/flutter中实现苹果IOS 26 毛玻璃效果、跟随滑动放大动画
flutter·ios·uni-app
2501_916007471 天前
Fastlane 结合 开心上架(Appuploader)命令行实现跨平台上传发布 iOS App 的完整方案
android·ios·小程序·https·uni-app·iphone·webview
CV大师杨某1 天前
如何在uni-app中禁用iOS橡皮筋效果?
ios·uni-app
2501_915918411 天前
iOS 上架应用市场全流程指南,App Store 审核机制、证书管理与跨平台免 Mac 上传发布方案(含开心上架实战)
android·macos·ios·小程序·uni-app·cocoa·iphone
C_philadd1 天前
Xcode26升级以后重要
ios
2501_915909061 天前
HTTPS 错误排查实战,从握手到应用层的工程化流程
网络协议·http·ios·小程序·https·uni-app·iphone
美狐美颜sdk2 天前
跨平台直播美颜sdk集成攻略:Android、iOS与Web的统一方案
android·前端·ios