I have NavigationStack presented as a sheet and I intend to dynamically adjust its height while pushing views within it. I'm utilizing a global observable variable to manage the height and everything works fine except that the height changes abruptly without any animation. It abruptly transitions from one height to another. The issue can be reproduced using the following code:
#Preview {
@Previewable @State var height: CGFloat = 200
Text("Parent View")
.sheet(isPresented: .constant(true)) {
NavigationStack {
Form {
NavigationLink("Button") {
RoundedRectangle(cornerRadius: 20)
.fill(Color.blue)
.frame(height: 200)
.navigationTitle("Child")
.onAppear {
withAnimation {
height = 300
}
}
}
}
.navigationTitle("Parent")
.navigationBarTitleDisplayMode(.inline)
.presentationDetents([.height(height)])
.onAppear {
withAnimation {
height = 150
}
}
}
}
}
The height of the sheet changes smoothly if you use the modifier presentationDetents(_:selection:)
to set the selected detent. The Set
should include all the possible sizes that you wish to use. With this approach, it is not even necessary to use withAnimation
when updating the selected detent.
Getting the child content to adjust height in an animated way is a bit more difficult. One technique is as follows:
GeometryReader
onChange
handler to update a state variable when the measured height changesmaxHeight
to the content inside the GeometryReader
.Here is the updated example with all changes applied:
struct ContentView: View {
let detents: Set<PresentationDetent> = [.height(150), .height(300)]
@State private var selectedDetent: PresentationDetent = .height(150)
@State private var actualHeight: CGFloat?
var body: some View {
Text("Parent View")
.sheet(isPresented: .constant(true)) {
NavigationStack {
Form {
NavigationLink("Button") {
GeometryReader { proxy in
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.navigationTitle("Child")
.frame(maxHeight: actualHeight)
.animation(.default, value: actualHeight)
.onChange(of: proxy.size.height) { _, newVal in
actualHeight = newVal
}
}
.onAppear {
selectedDetent = .height(300)
}
}
}
.navigationTitle("Parent")
.navigationBarTitleDisplayMode(.inline)
.presentationDetents(detents, selection: $selectedDetent)
.onAppear {
selectedDetent = .height(150)
}
}
}
}
}