Skip to content

Instantly share code, notes, and snippets.

@dankamel
Last active April 15, 2024 15:57
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dankamel/d7d66bea734ff1a2ea2a6f6abfd538ad to your computer and use it in GitHub Desktop.
Save dankamel/d7d66bea734ff1a2ea2a6f6abfd538ad to your computer and use it in GitHub Desktop.
Native iOS 15 Adjustable Bottom Sheet In SwiftUI (half bottom sheet)
import SwiftUI
struct BottomSheetDesign: View {
@State var showSheet: Bool? = nil
var body: some View {
Button(action: { showSheet = true }) {
HStack (spacing: 5) {
Image(systemName: "hand.tap.fill")
.font(.system(size: 20, weight: .regular, design: .rounded))
Text("me to bring up adjustable sheet")
.font(.system(size: 20, weight: .regular, design: .rounded))
}
}
.halfSheet(showSheet: $showSheet) {
ZStack {
Color.blue
ScrollView(.vertical, showsIndicators: false) {
VStack {
RoundedRectangle(cornerRadius: 12, style: .continuous)
.frame(width: 50, height: 8)
.padding(.top, -20)
.foregroundColor(.black.opacity(0.2))
Text("Hello half sheet 🥹")
.font(.system(size: 25, weight: .regular, design: .rounded))
.foregroundColor(.white)
.padding(.top, 70)
.padding(.bottom, 10)
Button(action: { showSheet = false }) {
HStack(spacing: 5) {
Image(systemName: "hand.tap.fill")
.font(.system(size: 20, weight: .bold, design: .rounded))
.foregroundColor(.white)
Text("me to close sheet")
.font(.system(size: 20, weight: .bold, design: .rounded))
.foregroundColor(.white)
}
}
}
.padding(.top, 30)
}
}
.edgesIgnoringSafeArea(.bottom)
} onDismiss: {
print("sheet dismissed")
}
}
}
struct BottomSheetDesign_Previews: PreviewProvider {
static var previews: some View {
BottomSheetDesign()
}
}
import SwiftUI
// Custom Half Sheet Modifier....
extension View {
//binding show bariable...
func halfSheet<Content: View>(
showSheet: Binding<Bool?>,
@ViewBuilder content: @escaping () -> Content,
onDismiss: @escaping () -> Void
) -> some View {
return self
.background(
HalfSheetHelper(sheetView: content(), showSheet: showSheet, onDismiss: onDismiss)
)
}
}
// UIKit integration
struct HalfSheetHelper<Content: View>: UIViewControllerRepresentable {
var sheetView: Content
let controller: UIViewController = UIViewController()
@Binding var showSheet: Bool?
var onDismiss: () -> Void
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func makeUIViewController(context: Context) -> UIViewController {
controller.view.backgroundColor = .clear
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
if let showSheet: Bool = showSheet {
if showSheet {
let sheetController = CustomHostingController(rootView: sheetView)
sheetController.presentationController?.delegate = context.coordinator
uiViewController.present(sheetController, animated: true)
}
}
}
//on dismiss...
final class Coordinator: NSObject, UISheetPresentationControllerDelegate {
var parent: HalfSheetHelper
init(parent: HalfSheetHelper) {
self.parent = parent
}
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
parent.showSheet = false
}
}
}
// Custom UIHostingController for halfSheet...
final class CustomHostingController<Content: View>: UIHostingController<Content> {
override func viewDidLoad() {
view.backgroundColor = .clear
if let presentationController = presentationController as? UISheetPresentationController {
presentationController.detents = [
.medium(),
.large()
]
//MARK: - sheet grabber visbility
presentationController.prefersGrabberVisible = false // i wanted to design my own grabber hehehe
// this allows you to scroll even during medium detent
presentationController.prefersScrollingExpandsWhenScrolledToEdge = false
//MARK: - sheet corner radius
presentationController.preferredCornerRadius = 30
// for more sheet customisation check out this great article https://sarunw.com/posts/bottom-sheet-in-ios-15-with-uisheetpresentationcontroller/#scrolling
}
}
}
public struct LazyView<Content: View>: View {
private let build: () -> Content
public init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
public var body: Content {
build()
}
}
@Frodothedwarf
Copy link

Nice code, just used it myself.

Works perfectly, i just have one addition.

In the updateUIViewController please add:

if showSheet {
                let sheetController = CustomHostingController(rootView: sheetView)
                sheetController.presentationController?.delegate = context.coordinator
                uiViewController.present(sheetController, animated: true)
            } else {
                uiViewController.dismiss(animated: true)
            }

So it is easy to dismiss from the view it is generated from.

@kellyhuberty
Copy link

Going to try this out! Btw, the onDismiss block gets passed around a lot, but doesn't seem to actually get called in this jist.

@UzBestDeveloper
Copy link

Why on tap sheet not being dismissed?

@bruijnesteijn
Copy link

For DISMISS use the suggestion above and add
else {
uiViewController.dismiss(animated: true)
}

And of course in your own 'sheet' (View) you have to set showSheet = false to trigger the update

Worked for me

@LiterallyDi
Copy link

aww , good

@kirillpukhov2004
Copy link

Excellent, but it doesn't work with EnvironmentObjects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment