I'm working on creating a view which uses matchedGeometryEffect, embedded within a NavigationView, and when presenting the second view another NavigationView.
The animation "works" and it matches correctly, however when it toggles from view to view it happens to swing as if unwinding from the navigation stack.
However, if I comment out the NavigationView on the secondary view the matched geometry works correctly.
I am working on iOS 14.0 and above.
Sample code
Model / Mock Data
struct Model: Identifiable {
let id = UUID().uuidString
let icon: String
let title: String
let account: String
let colour: Color
}
let mockItems: [Model] = [
Model(title: "Test title 1", colour: .gray),
Model(title: "Test title 2", colour: .blue),
Model(title: "Test title 3", colour: .purple)
...
]
Card View
struct CardView: View {
let item: Model
var body: some View {
VStack {
Text(item.title)
.font(.title3)
.fontWeight(.heavy)
}
.padding()
.frame(maxWidth: .infinity, minHeight: 100, alignment: .leading)
.background(item.colour)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
Secondary / Detail View
struct DetailView: View {
@Binding var isShowingDetail: Bool
let item: Model
let animation: Namespace.ID
var body: some View {
NavigationView { // <--- comment out here and it works
VStack {
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
withAnimation { isShowingDetail = false }
}
ScrollView(.vertical, showsIndicators: false) {
Text("Lorem ipsum dolor...")
}
}
.padding(.horizontal)
.navigationBarTitleDisplayMode(.inline)
}
.navigationViewStyle(.stack)
}
}
Primary View
struct ListView: View {
@State private var selectedCard: Model?
@State private var isShowingCard: Bool = false
@Namespace var animation
var body: some View {
ZStack {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
ForEach(mockItems) { item in
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
withAnimation {
selectedCard = item
isShowingCard = true
}
}
}
}
.navigationTitle("Test title")
}
.padding(.horizontal)
.navigationViewStyle(.stack)
// show detail view
if let selectedCard = selectedCard, isShowingCard {
DetailView(
isShowingDetail: $isShowingCard,
item: selectedCard,
animation: animation
)
}
}
}
}
Video examples
With NavigationView in DetailView
Without NavigationView in DetailView
Ignore the list view still visible
CodePudding user response:
You don't need second NavigationView (actually I don't see links at all, so necessity of the first one is also under question). Anyway we can just change the layout order and put everything into one NavigationView, like below.
Tested with Xcode 13.4 / iOS 15.5
struct ListView: View {
@State private var selectedCard: Model?
@State private var isShowingCard: Bool = false
@Namespace var animation
var body: some View {
NavigationView {
ZStack { // container !!
if let selectedCard = selectedCard, isShowingCard {
DetailView(
isShowingDetail: $isShowingCard,
item: selectedCard,
animation: animation
)
} else {
ScrollView(.vertical, showsIndicators: false) {
ForEach(mockItems) { item in
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
selectedCard = item
isShowingCard = true
}
}
}
.navigationTitle("Test title")
}
}
.padding(.horizontal)
.navigationViewStyle(.stack)
.animation(.default, value: isShowingCard) // << animated here !!
}
}
}
struct DetailView: View {
@Binding var isShowingDetail: Bool
let item: Model
let animation: Namespace.ID
var body: some View {
VStack {
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
isShowingDetail = false
}
ScrollView(.vertical, showsIndicators: false) {
Text("Lorem ipsum dolor...")
}
}
.padding(.horizontal)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}
}



