Created
May 1, 2023 10:42
-
-
Save ahmedAlmasri/c1f0c57f5de80a0d05471e671c604d30 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// BottomSheet.swift | |
// | |
// | |
// Created by Ahmad Almasri on 21/01/2023. | |
// | |
import SwiftUI | |
import SwiftUIX | |
struct BottomSheet<Content: View>: View { | |
@Environment(\.horizontalSizeClass) private var horizontalSizeClass | |
@Binding private var isPresented: Bool | |
@State private var showSheetContent = false | |
@GestureState private var translation: CGFloat = 0 | |
private let content: Content | |
private var hideShowDuration: DispatchTime { | |
DispatchTime.now() + 0.4 | |
} | |
init(isPresented: Binding<Bool>, @ViewBuilder content: () -> Content) { | |
self._isPresented = isPresented | |
self.content = content() | |
} | |
var body: some View { | |
GeometryReader { geo in | |
VStack(spacing: 0) { | |
content | |
.padding(.horizontal, horizontalSizeClass == .regular ? 32 : 0) | |
.padding(.horizontal) | |
} | |
.frame(width: geo.size.width, alignment: .top) | |
.background(Color.raisinBlack) | |
.cornerRadius(20) | |
.frame(height: geo.size.height, alignment: .bottom) | |
.offset(y: max((showSheetContent ? 0 : geo.size.height), translation)) | |
.background(sheetBackground) | |
.animation(.interactiveSpring(), value: translation) | |
.gesture( | |
DragGesture().updating($translation, body: { value, state, _ in | |
state = value.translation.height | |
}) | |
.onEnded({ value in | |
let snapDistance = geo.size.height * 0.1 | |
guard abs(value.translation.height) > snapDistance else { | |
return | |
} | |
let presented = value.translation.height < 0 | |
if !presented { | |
dismiss() | |
} | |
}) | |
) | |
} | |
.onAppear { presentSheetContent() } | |
.edgesIgnoringSafeArea(.all) | |
} | |
private var sheetBackground: some View { | |
Color.black.opacity(showSheetContent ? 0.5 : 0) | |
.onTapGesture { dismiss() } | |
} | |
// MARK: - Actions | |
private func dismiss() { | |
withAnimation(.easeOut) { | |
self.showSheetContent = false | |
DispatchQueue.main.asyncAfter(deadline: hideShowDuration) { | |
self.isPresented = false | |
} | |
} | |
} | |
private func presentSheetContent() { | |
withAnimation(.spring(response: 0.3, dampingFraction: 1, blendDuration: 0)) { | |
self.showSheetContent = true | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public extension View { | |
func bottomSheet<Content: View>(isPresented: Binding<Bool>, title: LocalizedStringKey? = nil, @ViewBuilder content: @escaping () -> Content) -> some View { | |
self.windowOverlay(isKeyAndVisible: isPresented) { | |
if isPresented.wrappedValue { | |
BottomSheet(isPresented: isPresented) { | |
HStack(spacing: 16) { | |
ZStack { | |
Circle() | |
.fill(Color.darkCharcoal) | |
.frame(width: 32, height: 32) | |
Image(systemName: "xmark") | |
.foregroundColor(.light) | |
} | |
.onTapGesture { | |
withAnimation(.easeOut) { | |
isPresented.wrappedValue = false | |
} | |
} | |
if let title = title { | |
Text(title) | |
.foregroundColor(.light) | |
.font(.h2) | |
} | |
Spacer() | |
} | |
.padding(.vertical) | |
content() | |
.padding(.bottom, 32) | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if DEBUG | |
struct BottomSheet_Previews: PreviewProvider { | |
static var previews: some View { | |
BottomSheetView() | |
} | |
struct BottomSheetView: View { | |
@State var isShow = false | |
var body: some View { | |
Button(action: { | |
isShow = true | |
}, label: { | |
Text("Show sheet") | |
}) | |
.bottomSheet(isPresented: $isShow, title: "Test") { | |
HStack { | |
VStack(alignment: .leading) { | |
Label("Info sheet ready", systemImage: "checkmark") | |
Label("Custom content", systemImage: "checkmark") | |
Label("Adaptive height", systemImage: "checkmark") | |
} | |
.foregroundColor(.light) | |
Spacer() | |
} | |
} | |
} | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment