I have a bottom sheet that I toggle on and off. When I go to close out the view, I noticed the parent view shifts. I've tried adjusting the variables and couldn't find the culprit.
I have suspicion the issue is occurring on .gesture
at the end of the bottom sheet class but I'm not sure. Is there anyway to stop the parent view from shifting when toggled?
BottomSheet
fileprivate enum Constants {
static let radius: CGFloat = 16
static let indicatorHeight: CGFloat = 6
static let indicatorWidth: CGFloat = 60
static let snapRatio: CGFloat = 0.25
static let minHeightRatio: CGFloat = 0.3
}
struct BottomSheetView<Content: View>: View {
@Binding var isOpen: Bool
let maxHeight: CGFloat
let minHeight: CGFloat
let content: Content
@GestureState private var translation: CGFloat = 0
private var offset: CGFloat {
isOpen ? 0 : maxHeight - minHeight
}
private var indicator: some View { // << toggling while open
RoundedRectangle(cornerRadius: Constants.radius)
.fill(Color.secondary)
.frame(
width: Constants.indicatorWidth,
height: Constants.indicatorHeight
).onTapGesture {
self.isOpen.toggle()
}
}
init(isOpen: Binding<Bool>, maxHeight: CGFloat, @ViewBuilder content: () -> Content) {
self.minHeight = maxHeight * Constants.minHeightRatio
self.maxHeight = maxHeight
self.content = content()
self._isOpen = isOpen
}
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.indicator.padding()
self.content
}
.frame(width: geometry.size.width, height: self.maxHeight, alignment: .top)
.background(Color(.secondarySystemBackground))
.cornerRadius(Constants.radius)
.frame(height: geometry.size.height, alignment: .bottom)
.offset(y: max(self.offset self.translation, 0))
.animation(.interactiveSpring())
.gesture(
DragGesture().updating(self.$translation) { value, state, _ in
state = value.translation.height
}.onEnded { value in
let snapDistance = self.maxHeight * Constants.snapRatio
guard abs(value.translation.height) > snapDistance else {
return
}
self.isOpen = value.translation.height < 0
}
)
}
}
}
View
struct TestView2: View {
@State private var showMenu = false
@State var deleteAccountSheet = false
var body: some View {
NavigationView{
VStack{
Text("click me")
.onTapGesture{
deleteAccountSheet = true
}
HStack{
Text( "this will move up in view" )
.font(.custom("OpenSans-Regular", size: 24))
}
Text("in the view")
}
}
.blur(radius: deleteAccountSheet ? 2 : 0) // blur when bottomsheet open
GeometryReader { geometry in {
BottomSheetView(
isOpen: self.$deleteAccountSheet,
maxHeight: geometry.size.height * 0.7
) {
Text("Give me one more click")
.onTapGesture{
print("click")
self.deleteAccountSheet = false
}
}
}().edgesIgnoringSafeArea(.all)
}
}
}
CodePudding user response:
The VStack
containing the text was being laid out vertically with the bottom sheet. You can use a ZStack to make sure that the bottom sheet is laid out on top of the VStack
containing text.
The height of the VStack
containing the text and the GeometryReader
is equal, this means the value of geometry.size.height
is really fullScreenHeight * 0.5 * 0.7
.
If the contents of the NavigationView
is nested in a ZStack
the geometry.size.height
will be the full height of the screen in this case. Changing from maxHeight: geometry.size.height * 0.7
to maxHeight: geometry.size.height * 0.5 * 0.7
will give you the same sizing of the bottom sheet that you had in the example you provided. Here's an example of how to update the View
which displays the bottom sheet to get this working:
struct TestView: View {
@State private var showMenu = false
@State var deleteAccountSheet = false
var body: some View {
NavigationView{
ZStack {
VStack {
Text("click me")
.onTapGesture{
deleteAccountSheet = true
}
HStack{
Text( "this will move up in view" )
.font(.custom("OpenSans-Regular", size: 24))
}
Text("in the view")
}
.blur(radius: deleteAccountSheet ? 2 : 0)
GeometryReader { geometry in
BottomSheetView(
isOpen: self.$deleteAccountSheet,
maxHeight: geometry.size.height * 0.5 * 0.7
) {
Text("Give me one more click")
.onTapGesture{
print("click")
self.deleteAccountSheet = false
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
}
}