Skip to content

Instantly share code, notes, and snippets.

@perpeer
Last active January 7, 2025 11:30
Show Gist options
  • Select an option

  • Save perpeer/6f77032dc12f227d8fda120419fa75e9 to your computer and use it in GitHub Desktop.

Select an option

Save perpeer/6f77032dc12f227d8fda120419fa75e9 to your computer and use it in GitHub Desktop.
Custom bottom sheet for iOS15, iOS16
public extension View {
func rSheet<Content: View>(
isPresented: Binding<Bool>,
detents: RPresentationDetent = .medium,
@ViewBuilder content: @escaping () -> Content
) -> some View {
self.modifier(
RPresentationDetentModifier(
isPresented: isPresented,
detents: detents,
sheetContent: content
)
)
}
}
public enum RPresentationDetent {
case medium
case large
case height(CGFloat)
case fraction(CGFloat)
@available(iOS 16.0, *)
fileprivate var newDetents: Set<PresentationDetent> {
switch self {
case .medium:
return [.medium]
case .large:
return [.large]
case .height(let height):
return [.height(height)]
case .fraction(let fraction):
return [.fraction(fraction)]
}
}
fileprivate var oldDetents: [UISheetPresentationController.Detent] {
switch self {
case .medium, .height, .fraction:
return [.medium()]
case .large:
return [.large()]
}
}
}
fileprivate struct RPresentationDetentModifier<SheetContent: View>: ViewModifier {
@Binding private var isPresented: Bool
private let detents: RPresentationDetent
private let sheetContent: () -> SheetContent
init(
isPresented: Binding<Bool>,
detents: RPresentationDetent,
@ViewBuilder sheetContent: @escaping () -> SheetContent
) {
self._isPresented = isPresented
self.detents = detents
self.sheetContent = sheetContent
}
func body(content: Content) -> some View {
if #available(iOS 16.0, *) {
content
.sheet(isPresented: $isPresented) {
sheetContent()
.presentationDetents(detents.newDetents)
.presentationDragIndicator(.visible)
}
} else {
content
.background(
BottomSheetModifier(
isPresented: $isPresented,
detents: detents.oldDetents,
content: sheetContent
)
)
}
}
}
fileprivate struct BottomSheetModifier<Content: View>: UIViewControllerRepresentable {
private let isPresented: Binding<Bool>
private let detents: [UISheetPresentationController.Detent]
private let content: Content
init(
isPresented: Binding<Bool>,
detents: [UISheetPresentationController.Detent],
@ViewBuilder content: @escaping () -> Content
) {
self.isPresented = isPresented
self.detents = detents
self.content = content()
}
func makeUIViewController(context: Context) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
if isPresented.wrappedValue {
let hostingController = UIHostingController(rootView: content)
hostingController.modalPresentationStyle = .pageSheet
if let sheet = hostingController.sheetPresentationController {
sheet.detents = detents
sheet.prefersGrabberVisible = true
sheet.prefersEdgeAttachedInCompactHeight = false
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = false
}
uiViewController.present(hostingController, animated: true) {
isPresented.wrappedValue = false
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment