I’m working on a SwiftUI sheet that can resize itself based on the content’s height. The sheet works well until I introduce multi-line text.
To handle the dynamic height, I added .fixedSize(horizontal: false, vertical: true)
to the SheetHeightModifier
, which fixes the height issue. However, when I do this, I notice an odd animation behavior with the button. It jumps up when you tap it. Here’s my code.
Why would the button exhibit this behavior with the fixedSize modifier?
struct SheetHeightModifier: ViewModifier {
@Binding var height: CGFloat
func body(content: Content) -> some View {
content
.fixedSize(horizontal: false, vertical: true)
.background(
GeometryReader { reader -> Color in
height = reader.size.height
print(height)
return Color.clear
}
)
}
}
struct PresentationDetentModifier: ViewModifier {
@Binding var height: CGFloat
func body(content: Content) -> some View {
content
.modifier(SheetHeightModifier(height: $height))
.presentationDetents([.height(height)])
}
}
extension View {
func flexiblePresentationDetents(height: Binding<CGFloat>) -> some View {
self.modifier(PresentationDetentModifier(height: height))
}
}
struct ContentView: View {
@State var showSheet = false
@State var sheetHeight: CGFloat = 0
var body: some View {
VStack {
Button("Present Sheet") {
showSheet = true
}
}
.sheet(isPresented: $showSheet) {
VStack {
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
}
.padding()
.flexiblePresentationDetents(height: $sheetHeight)
}
.padding()
}
}
2
Answers
Looking at the logs, the height read by the geometry reader is initially more than 8000pt for a split second, then changes to about 200, which is correct.
Because of this large height, the view presenting the sheet shrinks a bit (similar to when you use the
.large
detent), then very quickly after that the height goes back down and so the view moves back to the initial position. This causes the views to jiggle a bit because they are being laid out again.A simple way to work around this is to just filter out these nonsensical heights from the geometry reader. If you are sure that the content size will never exceed some value, then you can ignore heights above that value. For example, here I have used 2000
Or you can use
min
to limit the height to a value that won’t cause the presenting view to move:Unfortunately, unlike in this post, you cannot use a
.custom
detent, because they cannot take any parameters. While they do have access toEnvironmentValues
, they are of the environment of the presenting view, not the sheet.I suspect that the reason why the height of the
Text
starts off so large is because the width is very small. This may be because it is performing some kind of zoom animation as the sheet appears.It helps if you apply a min width to the
content
insideSheetHeightModifier
. The minimum width is not necessarily the width of the screen, because a sheet on a large iPad does not occupy the full screen width. But it works quite well to use the width of an iPod (= 320) as min width: