I’d like to have a SwiftUI sheet that either shows the header or complete content.
Requiring iOS 16 is ok.
I already get the correct two measured heights a presentationDetents
import Foundation
import SwiftUI
struct ContentView: View {
@State private var showSheet = false
@State private var headerSize: CGSize = .zero
@State private var overallSize: CGSize = .zero
var body: some View {
Button("View sheet") {
showSheet = true
}
.sheet(isPresented: $showSheet) {
Group {
VStack(alignment: .leading) {
Text("Header")
.background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: HeaderSizePreferenceKey.self, value: geometryProxy.size)
}
)
Text("")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
Text("Some very long text ...")
} // VStack
.padding()
.background(
//measure without spacer
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
Spacer()
} // Group
.onPreferenceChange(SizePreferenceKey.self) { newSize in
overallSize.height = newSize.height
}
.onPreferenceChange(HeaderSizePreferenceKey.self) { newSize in
headerSize.height = newSize.height
}
.presentationDetents([
.height(headerSize.height),
.height(overallSize.height)
]
)
} // sheet content
}
}
struct HeaderSizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
}
struct MySheet_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This code is based on ideas from Make sheet the exact size of the content inside
It almost works. This is the complete content size:
This is the header content size:
What can I do that the last case shows the top of the content ("Header") instead of the center of the content?
2
Answers
So you want this:
The sheet content’s intrinsic height includes both the header and the body (the “Some very long text” lines). The problem to solve: when the sheet is at the header detent, the content doesn’t fit in the height, and draws itself center-aligned. One way to solve this is to put the content inside a container that draws its children top-aligned.
I tried
ZStack(alignment: .top)
, and I tried adding a.frame(alignment: .top)
modifier, but neither worked. I discovered thatGeometryReader
does what we need.I also restructured the sheet content so the header’s height is measured including vertical padding.
I had the same issue. I solved this by using a ScrollView with no axes.