I have a view that transitions in from the bottom. It's a ZStack, so it has a Child View as well.
struct ContentView: View {
@State private var presentParent = false
var body: some View {
if presentParent {
ZStack {
Rectangle()
.foregroundColor(.blue)
.frame(width: 300, height: 200)
ChildView()
}
.transition(.move(edge: .bottom))
} else {
Color.clear
.onAppear {
withAnimation(.linear(duration: 2)) {
presentParent = true
}
}
}
}
}
struct ChildView: View {
@State private var presentChild = false
var body: some View {
Text("EXAMPLE")
.font(.largeTitle)
.foregroundStyle(.red)
}
}
That works just fine. The ChildView moves in from the bottom with the Parent.
However, if my ChildView has its own transition, it no longer moves in from the bottom. it just slides in from the right while the Parent slides in from the bottom.
NEW ChildView code:
struct ChildView: View {
@State private var presentChild = false
var body: some View {
if presentChild {
Text("EXAMPLE")
.font(.largeTitle)
.foregroundStyle(.red)
.transition(.move(edge: .trailing))
} else {
Color.clear
.onAppear {
withAnimation {
presentChild = true
}
}
}
}
}
I've toyed around with using DispatchQueue.main.asyncAfter, but this feels kinda hacky, especially if I'm going to have several different nested ChildViews. Also, it doesn't achieve what I'm going for.
DESIRED OUTPUT: I would like to see the EXAMPLE text slide in from the right, while it is moving up from the bottom with the ParentView.
First, a few observations:
ZStack
instead of to the Rectangle
then the transition starts with the blue rectangle already visible, like in the first version. So the height of the ZStack
is what determines the start position for the blue rectangle.The animations can be brought together by adding .drawingGroup
to the parent. To see everything moving together, it helps if the duration of the child animation is the same as the parent, otherwise it finishes much earlier.
The transition will start off-screen if maxHeight: .infinity
is set on the ZStack
, but then there is an initial delay while the blank top-half of the screen slides in. Also, much of the child animation gets missed, because it happens off-screen. So to have it start without delay, you can use a GeometryReader
to measure the size of the screen and set a height on the ZStack
that omits the blank space above the rectangle.
Like this:
struct ContentView: View {
@State private var presentParent = false
let parentHeight: CGFloat = 200
var body: some View {
GeometryReader { screen in
if presentParent {
ZStack {
Color.blue
ChildView()
}
.frame(width: 300, height: parentHeight)
.drawingGroup()
.frame(
maxWidth: .infinity,
maxHeight: (screen.size.height + parentHeight) / 2,
alignment: .bottom
)
.transition(.move(edge: .bottom))
}
}
.onAppear {
withAnimation(.linear(duration: 2)) {
presentParent = true
}
}
}
}
struct ChildView: View {
@State private var presentChild = false
var body: some View {
if presentChild {
Text("EXAMPLE")
.font(.largeTitle)
.foregroundStyle(.red)
.transition(.move(edge: .trailing))
} else {
Color.clear
.onAppear {
withAnimation(.linear(duration: 2)) {
presentChild = true
}
}
}
}
}