Categories
Programming

Transitions in ZStacks

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.