Last active
April 15, 2024 15:57
-
-
Save dankamel/d7d66bea734ff1a2ea2a6f6abfd538ad to your computer and use it in GitHub Desktop.
Native iOS 15 Adjustable Bottom Sheet In SwiftUI (half bottom sheet)
This file contains 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
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() | |
} | |
} |
This file contains 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
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() | |
} | |
} |
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.
Why on tap sheet not being dismissed?
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
aww , good
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
Nice code, just used it myself.
Works perfectly, i just have one addition.
In the updateUIViewController please add:
So it is easy to dismiss from the view it is generated from.