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。


参考链接:

相关推荐
Antonio9151 小时前
【Swift】 UIKit:UIGestureRecognizer和UIView Animation
开发语言·ios·swift
私人珍藏库4 小时前
利用 iPhone 或 Apple Watch ,自动锁定和解锁 Windows
ios·iphone
2501_9160088910 小时前
iOS 性能测试的深度实战方法 构建从底层指标到真实场景回放的多工具测试体系
android·ios·小程序·https·uni-app·iphone·webview
kk哥889910 小时前
iOS 26 适配指南:UIScrollView 新特性与最佳实践
macos·ios·cocoa
iOS阿玮11 小时前
屁股坏了,我硬抗了3天,差不多省了几千块这件事儿。
ios
alloc16 小时前
Foundation Models Framework
ios
ajassi200020 小时前
开源 Objective-C IOS 应用开发(二十三).a静态库的封装和使用
ios·开源·objective-c
明远湖之鱼21 小时前
浅入理解流式SSR的性能收益与工作原理
前端·ios
白玉cfc21 小时前
【iOS】多线程基础
macos·ios
2501_915909061 天前
iOS APP 抓包全流程解析,HTTPS 调试、网络协议分析与多工具组合方案
android·ios·小程序·https·uni-app·iphone·webview