I'm trying to show ZStack with animation from bottom when View is presented on screen. Code below doesn't work. I'm new and I don't know what is wrong with this? Where the withAnimation should be, and where transition ?
Code looks like this:
ZStack {
RoundedRectangle(cornerRadius: 15, style: .continuous)
.fill(.white)
.frame(width:UIScreen.main.bounds.width - 32, height: 250)
.background(.ultraThinMaterial,
in: RoundedRectangle(cornerRadius: 15, style: .circular))
.opacity(0.4)
.shadow(radius: 5, x:0, y: 3)
VStack(alignment: .leading, spacing: 10) {
Text("Login")
.font(.title3)
.foregroundColor(Color(.systemGray))
TextField("Enter login", text: $login)
.textFieldStyle(.roundedBorder)
.submitLabel(.continue)
Text("Password")
.font(.title3)
.foregroundColor(Color(.systemGray))
SecureField("Enter password", text: $password)
.textFieldStyle(.roundedBorder)
.submitLabel(.done)
Spacer()
Button(action: {
}, label: {
Text("Enter")
.frame(maxWidth: .infinity, maxHeight: 40)
.background(Color(.systemYellow))
.clipShape(.buttonBorder)
.foregroundStyle(Color(.black))
.opacity(0.6)
})
}
.padding()
.frame(width:UIScreen.main.bounds.width - 32, height: 250)
}
.padding()
.onAppear {
withAnimation(.easeInOut(duration: 5)) {
isPresented.toggle()
}
}
.transition(AnyTransition.move(edge: .bottom))
.zIndex(0)
}
There is no transition happening because the content is already visible when it first appears.
To fix:
ZStack inside some other container (a Group will do).onAppear callback to the container.ZStack conditional on the state variable isPresented.Currently, the flag isPresented is being toggled in .onAppear. There is a possibility, that this may cause it to disappear when presented a second time (depending on whether the state variable retains its state). I would suggest setting it explicitly to true instead. You could use an .onDisappear callback to set it back to false, if you need to.
The zIndex might not be needed either, try deleting it.
Group {
if isPresented {
ZStack {
// ...
}
.padding()
.transition(AnyTransition.move(edge: .bottom))
}
}
.onAppear {
withAnimation(.easeInOut(duration: 5)) {
isPresented = true
}
}
.onDisappear {
isPresented = false
}
EDIT Following from your comment, here is a fully updated standalone example to demonstrate it working.
On the issue of the flag, I suspect that what is happening is that you are toggling isPresented somewhere else in your code. In the example below, I have deliberately renamed the flag to isShowing, so that there is no chance of it being updated from elsewhere.
Regarding the animation, if you want the panel to slide up from the bottom edge of the screen then extra (empty) height needs to be added, equivalent to the space between the panel and the bottom edge. This extra height can be computed if the height of the screen is known.
The updated code below also includes the following changes:
GeometryReader to measure it, because UIScreen.main is deprecated. Also, UIScreen.main doesn't work well with iPad split-screen. So the top-level container is now a GeometryReader.GeometryReader is only needed for finding the height of the screen. If you want the panel to use the full width - 32pt, then you can just set maxWidth: .infinity and apply horizontal padding of 16pt.ZStack, the RoundedRectangle can simply be applied as .background to the VStack. This way, it automatically has the same size..foregroundColor is also deprecated, use .foregroundStyle instead.Color from a UIColor. For the standard colors, you can just use the definitions provided by Color.struct ContentView: View {
private let panelHeight: CGFloat = 250
@State private var isShowing = false
@State private var login = ""
@State private var password = ""
var body: some View {
GeometryReader { screen in
if isShowing {
VStack(alignment: .leading, spacing: 10) {
Text("Login")
.font(.title3)
.foregroundStyle(.gray)
TextField("Enter login", text: $login)
.textFieldStyle(.roundedBorder)
.submitLabel(.continue)
Text("Password")
.font(.title3)
.foregroundStyle(.gray)
SecureField("Enter password", text: $password)
.textFieldStyle(.roundedBorder)
.submitLabel(.done)
Spacer()
Button {} label: {
Text("Enter")
.frame(maxWidth: .infinity, maxHeight: 40)
.background(.yellow)
.clipShape(.buttonBorder)
.foregroundStyle(.black)
.opacity(0.6)
}
}
.padding()
.frame(height: panelHeight)
.frame(maxWidth: .infinity)
.background {
RoundedRectangle(cornerRadius: 15)
.fill(Color(white: 0.94))
.shadow(radius: 5, x:0, y: 3)
}
.padding(.horizontal, 16)
.frame(
height: (screen.size.height + panelHeight) / 2,
alignment: .bottom
)
.transition(.move(edge: .bottom))
}
}
.ignoresSafeArea()
.onAppear {
withAnimation(.easeInOut(duration: 5)) {
isShowing = true
}
}
.onDisappear {
isShowing = false
}
}
}
