I've caught a very strange problem (THAT ONLY OCCURS ON REAL DEVICES) that has to do with using @NameSpace and MatchedGeometryEffect using NavigationStack. The problem is that if I go from the first screen to the second screen (on the second screen I have a View with MatchedGeometryEffect) , I get a strange bug where the MatchedGeometryEffect animation starts to work very badly (ONLY ON REAL DEVICE), it feels like the animation frame count drops to a minimum, but on simulator or preview everything works fine as it should. However, if I use the screen without NavigationStack, there is no such animation problem. What can this be related to ? And how can this be fixed ? It only takes a couple of lines of code to catch the animation problem, but it took all day to figure out what the problem is.
FirstView
struct NameSpaceTest2Navigation: View {
@State private var nameSpaceTest2Path: [String] = []
var body: some View {
NavigationStack {
Button(action: {
nameSpaceTest2Path.append("nameSpaceTest2")
}, label: {
Text("Button")
})
.navigationDestination(for: String.self) { path in
NameSpaceTest2()
}
}
}
}
Second View
struct NameSpaceTest2: View {
@State private var selection: Int = 0
var body: some View {
VStack {
TabView(selection: $selection) {
ForEach(0..<5, id: \.self) { _ in
Color.white
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.overlay(alignment: .top) {
NameSpaceTest2Header(selection: $selection)
}
}
}
Third View
struct NameSpaceTest2Header: View {
@Binding var selection: Int
@Namespace var sectionUnderline
var body: some View {
ScrollViewReader { scrollReader in //START: ScrollViewReader
ScrollView(.horizontal, showsIndicators: false) { //START: ScrollView
HStack(spacing: 0) { //START: HStack
ForEach(0..<5, id: \.self) { index in
VStack { //START: VStack
Text("Name: \(index)")
.foregroundStyle(Color.green)
.padding(.horizontal, 24)
.padding(.vertical, 15)
.overlay(alignment: .bottom) {
if selection == index {
Rectangle()
.fill(Color.red)
.frame(height: 5)
.matchedGeometryEffect(id: "sectionUnderline", in: sectionUnderline, properties: .frame)
.transition(.scale(scale: 1))
}
}
.animation(.smooth, value: selection)
} //END: VStack
.onTapGesture {
withAnimation(.smooth) {
scrollReader.scrollTo(index)
selection = index
}
}
.tag(index)
}
} //END: HStack
} //END: ScrollView
.onChange(of: selection, perform: { value in
withAnimation(.smooth) {
scrollReader.scrollTo(value, anchor: .center)
}
})
} //END: ScrollViewReader
}
}
Try to repeat this code and run it on a real device with NavigationStack and you will get a bug with the animation, it will twitch like it has 5-10 frames per second.
Then try to run Second View without Navigation Stack and you will get smooth animation, which is what it should be.
What could be the problem ? How to get the smooth animation back ?
Once again, you should only test on a real device.