Skip to content

Instantly share code, notes, and snippets.

@VuHD
Forked from Kies8/ZoomTransitionDemoApp.swift
Created January 8, 2026 15:31
Show Gist options
  • Select an option

  • Save VuHD/bd7f3bcfcf09b9dbbf6418f4d3973b11 to your computer and use it in GitHub Desktop.

Select an option

Save VuHD/bd7f3bcfcf09b9dbbf6418f4d3973b11 to your computer and use it in GitHub Desktop.
import SwiftUI
import Charts
// MARK: - Data Model
struct MetricData: Identifiable {
let id = UUID()
let day: String
let value: Double
}
let sampleData = [
MetricData(day: "Mon", value: 45),
MetricData(day: "Tue", value: 67),
MetricData(day: "Wed", value: 82),
MetricData(day: "Thu", value: 71),
MetricData(day: "Fri", value: 89),
MetricData(day: "Sat", value: 95),
MetricData(day: "Sun", value: 78)
]
// MARK: - Glass Card Component
struct GlassCard<Content: View>: View {
@Environment(\.colorScheme) var colorScheme
private let content: Content
private let padding: CGFloat
init(padding: CGFloat = 16, @ViewBuilder content: () -> Content) {
self.padding = padding
self.content = content()
}
private var borderGradient: LinearGradient {
if colorScheme == .dark {
return LinearGradient(
stops: [
.init(color: .white.opacity(0.15), location: 0),
.init(color: .clear, location: 0.4),
.init(color: .black.opacity(0.5), location: 1.0)
],
startPoint: .top,
endPoint: .bottom
)
} else {
return LinearGradient(
stops: [
.init(color: .white.opacity(0.6), location: 0),
.init(color: .clear, location: 0.4),
.init(color: .black.opacity(0.05), location: 1.0)
],
startPoint: .top,
endPoint: .bottom
)
}
}
private let cornerRadius: CGFloat = 36
var body: some View {
content
.padding(padding)
.background(
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.fill(Color(uiColor: .secondarySystemGroupedBackground))
// ⚠️ PROBLEM: This shadow gets animated during zoom transition
.shadow(
color: .black.opacity(colorScheme == .dark ? 0.3 : 0.05),
radius: 12,
x: 0,
y: 6
)
)
.overlay(
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.stroke(borderGradient, lineWidth: 2)
)
.contentShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
}
}
// MARK: - Mini Chart Component
struct MiniMetricChart: View {
let data: [MetricData]
let color: Color
var body: some View {
Chart {
ForEach(data) { item in
LineMark(
x: .value("Day", item.day),
y: .value("Value", item.value)
)
.interpolationMethod(.catmullRom)
.foregroundStyle(color)
.lineStyle(StrokeStyle(lineWidth: 2.5))
}
}
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.frame(height: 40)
.padding(.vertical, 8)
}
}
// MARK: - Metric Card Component
struct MetricCard: View {
let title: String
let subtitle: String
let icon: String
let value: String
let unit: String
let color: Color
let data: [MetricData]
var body: some View {
GlassCard(padding: 16) {
VStack(alignment: .leading, spacing: 2) {
// Header
HStack {
Label(title, systemImage: icon)
.font(.system(.headline, design: .rounded, weight: .bold))
.foregroundStyle(.primary)
Spacer()
Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(.tertiary)
}
Text(subtitle)
.font(.caption)
.foregroundStyle(.secondary)
// Value
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(value)
.font(.system(.title2, design: .rounded)).bold()
Text(unit)
.font(.system(.callout, design: .rounded, weight: .semibold))
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 4)
MiniMetricChart(data: data, color: color)
}
}
}
}
// MARK: - Detail View
struct MetricDetailView: View {
let title: String
let data: [MetricData]
let color: Color
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
VStack(alignment: .leading) {
Text("Weekly Average")
.font(.subheadline)
.foregroundStyle(.secondary)
.textCase(.uppercase)
Text(title)
.font(.system(.largeTitle, design: .rounded, weight: .bold))
}
.padding(.horizontal)
// Large chart
Chart(data) { item in
AreaMark(
x: .value("Day", item.day),
y: .value("Value", item.value)
)
.foregroundStyle(color.opacity(0.1).gradient)
.interpolationMethod(.catmullRom)
LineMark(
x: .value("Day", item.day),
y: .value("Value", item.value)
)
.foregroundStyle(color)
.lineStyle(StrokeStyle(lineWidth: 4))
.interpolationMethod(.catmullRom)
}
.frame(height: 300)
.padding()
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.hidden, for: .navigationBar)
// ⚠️ PROBLEM: Tab bar reappears with ~1 second delay on dismiss
.toolbar(.hidden, for: .tabBar)
}
}
// MARK: - Dashboard Grid View
struct DashboardGridView: View {
let namespace: Namespace.ID
private let columns = [
GridItem(.flexible(), spacing: 8),
GridItem(.flexible(), spacing: 8)
]
var body: some View {
LazyVGrid(columns: columns, spacing: 8) {
NavigationLink(value: "tasks") {
MetricCard(
title: "Tasks",
subtitle: "Last 7 days",
icon: "checkmark.circle",
value: "42",
unit: "done",
color: .blue,
data: sampleData
)
// ⚠️ Using matchedTransitionSource for zoom animation
.matchedTransitionSource(id: "tasks-card", in: namespace)
}
.buttonStyle(.plain)
NavigationLink(value: "steps") {
MetricCard(
title: "Steps",
subtitle: "Last 7 days",
icon: "figure.walk",
value: "8,453",
unit: "avg",
color: .green,
data: sampleData.map { MetricData(day: $0.day, value: $0.value * 100) }
)
.matchedTransitionSource(id: "steps-card", in: namespace)
}
.buttonStyle(.plain)
}
.padding(.horizontal, 16)
}
}
// MARK: - Main Content View
struct ZoomTransitionDemoContent: View {
@Namespace private var namespace
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 20) {
Text("Zoom Transition Issue Demo")
.font(.title2.bold())
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16)
.padding(.top, 20)
Text("Tap a card and watch the shadow + tab bar when you go back")
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16)
DashboardGridView(namespace: namespace)
}
.padding(.bottom, 100)
}
.background(Color(uiColor: .systemGroupedBackground))
.navigationDestination(for: String.self) { selection in
if selection == "tasks" {
MetricDetailView(
title: "Tasks Completed",
data: sampleData,
color: .blue
)
// ⚠️ Using zoom transition - this is where the problem occurs
.navigationTransition(
.zoom(sourceID: "tasks-card", in: namespace)
)
} else if selection == "steps" {
MetricDetailView(
title: "Steps Walked",
data: sampleData.map { MetricData(day: $0.day, value: $0.value * 100) },
color: .green
)
.navigationTransition(
.zoom(sourceID: "steps-card", in: namespace)
)
}
}
}
}
}
// MARK: - Tab View Wrapper
struct ZoomTransitionDemoApp: View {
var body: some View {
TabView {
ZoomTransitionDemoContent()
.tabItem {
Label("Home", systemImage: "house")
}
Text("Tab 2")
.tabItem {
Label("Other", systemImage: "star")
}
Text("Tab 3")
.tabItem {
Label("Settings", systemImage: "gear")
}
}
}
}
// MARK: - Preview
#Preview {
ZoomTransitionDemoApp()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment