Skip to content

Instantly share code, notes, and snippets.

@philipborbon
Forked from mecid/BottomSheetView.swift
Created August 19, 2020 10:47
Show Gist options
  • Save philipborbon/8986b84247678d0ac08eed7cb69c4d37 to your computer and use it in GitHub Desktop.
Save philipborbon/8986b84247678d0ac08eed7cb69c4d37 to your computer and use it in GitHub Desktop.
//
// BottomSheetView.swift
//
// Created by Majid Jabrayilov
// Copyright © 2019 Majid Jabrayilov. All rights reserved.
//
import SwiftUI
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 {
@State private var isOpen: Bool = false
let maxHeight: CGFloat
let minHeight: CGFloat
let keyboardOffset: CGFloat
let content: Content
@GestureState private var translation: CGFloat = 0
private var offset: CGFloat {
return max (
(offsetForState + self.translation > self.maxHeight - self.minHeight)
? self.maxHeight - self.minHeight
: offsetForState + self.translation
, 0
)
}
private var offsetForState: CGFloat {
isOpen ? 0 : self.maxHeight - self.minHeight
}
private var grabControlIndicator: some View {
if self.keyboardOffset < 0 {
return AnyView(EmptyView())
} else {
return AnyView (
VStack {
RoundedRectangle(cornerRadius: Constants.radius)
.fill(Theme.secondaryBackgroundColor)
.frame(
width: Constants.indicatorWidth,
height: Constants.indicatorHeight
)
.onTapGesture {
self.isOpen.toggle()
}
}
)
}
}
init (
// isOpen: Binding<Bool>,
minHeight: CGFloat,
maxHeight: CGFloat,
keyboardOffset: CGFloat,
@ViewBuilder content: () -> Content
) {
// self.minHeight = maxHeight * Constants.minHeightRatio
self.minHeight = minHeight
self.maxHeight = maxHeight
self.keyboardOffset = keyboardOffset
self.content = content()
// self._isOpen = isOpen
}
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.grabControlIndicator.padding()
Rectangle()
.fill(Theme.secondaryBackgroundColor)
.frame(height: 1)
.edgesIgnoringSafeArea(.horizontal)
self.content
}
.frame(width: geometry.size.width, height: self.maxHeight, alignment: .top)
.background ( RoundedCorner (
tl: self.isOpen ? 0 : Constants.radius,
tr: self.isOpen ? 0 : Constants.radius,
bl: 0,
br: 0
)
.fill(Theme.backgroundColor))
.frame(height: geometry.size.height, alignment: .bottom)
.offset(y: self.offset)
.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
}
)
}
}
}
#if DEBUG
struct BottomSheetView_Previews: PreviewProvider {
static var previews: some View {
BottomSheetView (
// isOpen: .constant(false),
minHeight: 250,
maxHeight: 600,
keyboardOffset: 0
) {
Rectangle().fill(Color.blue)
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment