Skip to content

Instantly share code, notes, and snippets.

@phillipcaudell
Created September 21, 2023 14:17
Show Gist options
  • Save phillipcaudell/c7135ec8d7933e391fc6223448a6af0a to your computer and use it in GitHub Desktop.
Save phillipcaudell/c7135ec8d7933e391fc6223448a6af0a to your computer and use it in GitHub Desktop.
A presentation style that displays the content centered in the screen smaller than a regular sheet.
import SwiftUI
extension View {
/// Presents a form sheet when a binding to a Boolean value that you provide is true.
/// - Parameters:
/// - isPresented: A binding to a Boolean value that determines whether
/// to present the sheet that you create in the modifier's
/// `content` closure.
/// - interactiveDismiss: A Boolean value that indicates whether to
/// prevent nonprogrammatic dismissal.
/// - content: A closure that returns the content of the sheet.
public func formSheet<Content>(
isPresented: Binding<Bool>,
interactiveDismiss: Bool = true,
@ViewBuilder content: @escaping () -> Content
) -> some View where Content : View {
self.background {
FormSheetPresentable(
isPresenting: isPresented,
interactiveDismiss: interactiveDismiss,
content: content
)
}
}
}
fileprivate struct FormSheetPresentable<Content>: UIViewControllerRepresentable where Content : View {
@Binding var isPresenting: Bool
let interactiveDismiss: Bool
@ViewBuilder let content: () -> Content
@State private var contentViewController: UIViewController?
func makeUIViewController(context: Context) -> some UIViewController {
UIViewController()
}
func updateUIViewController(_ viewController: UIViewControllerType, context: Context) {
if isPresenting {
guard self.contentViewController == nil else {
return
}
let contentViewController = makeContentViewController()
contentViewController.presentationController?.delegate = context.coordinator
viewController.present(contentViewController, animated: true)
contentViewController.isModalInPresentation = !interactiveDismiss
setContentViewController(contentViewController)
} else {
guard let contentViewController else {
return
}
contentViewController.dismiss(animated: true)
setContentViewController(nil)
}
}
func setContentViewController(_ viewController: UIViewController?) {
DispatchQueue.main.async {
self.contentViewController = viewController
}
}
func makeContentViewController() -> UIViewController {
let viewController = UIHostingController(rootView: content())
viewController.modalPresentationStyle = .formSheet
return viewController
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
extension FormSheetPresentable {
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
let parent: FormSheetPresentable
init(_ parent: FormSheetPresentable) {
self.parent = parent
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.parent.isPresenting = false
self.parent.contentViewController = nil
}
}
}
struct FormSheetPreview: View {
@State private var show = false
var body: some View {
Button {
show.toggle()
} label: {
Text("\(show ? "Hide" : "Show") Sheet")
}
.formSheet(isPresented: $show) {
Text("Hello there")
}
}
}
#Preview {
FormSheetPreview()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment