The other day, I found a fun little SwiftUI edge case: using a .transition()
modifier in a ZStack
doesn’t work. As a basic demo, let’s do something like this:
struct DemoView: View {
@State var showing = true
var body: some View {
ZStack {
Rectangle().fill(Color.red)
.onTap {
showing.toggle()
}
if (showing) {
Text("Hello, world!")
.transition(.opacity)
}
}
}
}
Pretty simple example, yeah? Seems like it’d Just Work(TM) out of the box. It doesn’t, though; instead of a lovely opacity animation, it just pops in and out. Hmm.
Worry not, though, as I have a fix:
struct DemoView: View {
@State var showing = true
var body: some View {
ZStack {
Rectangle().fill(Color.red)
.onTap {
showing.toggle()
}
.zIndex(1)
if (showing) {
Text("Hello, world!")
.transition(.opacity)
.zIndex(2)
}
}
}
}
Et voila, your transition works!
Now, I haven’t exactly done rigorous experimentation to figure out why, exactly, this works, but I do have a theory: when SwiftUI removes the Text
, it loses its place in the ZStack
, and gets assigned to “behind everything else.” By manually specifying the order, the slot stays reserved, and it can stay there while it fades in and out.