Skip to content

Instantly share code, notes, and snippets.

@kaishin

kaishin/StickySheet.swift

Last active Mar 1, 2021
Embed
What would you like to do?
StickySheet – A sticky sheet implementation for SwiftUI
import SwiftUI
public final class StickySheetState: ObservableObject {
@Published public var dismissAttempted: Bool = false
}
struct StickySheetShouldDismissPreferenceKey: PreferenceKey {
typealias Value = Bool
static var defaultValue: Value = false
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
struct StickySheetWrapper<Content: View>: UIViewControllerRepresentable {
@Binding var shouldDismiss: Bool
@Binding var attempted: Bool
var contentView: Content
typealias UIViewControllerType = StickySheetHostingController<Content>
init(shouldDismiss: Binding<Bool>, attempted: Binding<Bool>, @ViewBuilder content: @escaping () -> Content) {
self.contentView = content()
self._shouldDismiss = shouldDismiss
self._attempted = attempted
}
func makeUIViewController(context: UIViewControllerRepresentableContext<StickySheetWrapper>) -> UIViewControllerType {
return StickySheetHostingController(rootView: contentView, shouldDimiss: shouldDismiss)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
uiViewController.rootView = contentView
uiViewController.shouldDismiss = shouldDismiss
uiViewController.stickySheetDelegate = context.coordinator
}
func makeCoordinator() -> StickySheetWrapper<Content>.Coordinator {
return Coordinator(attempted: $attempted)
}
class Coordinator: NSObject, StickySheetDelegate {
@Binding var attempted: Bool
init(attempted: Binding<Bool>) {
self._attempted = attempted
}
func updateAttempted(flag: Bool) {
self.attempted = flag
}
}
}
protocol StickySheetDelegate {
func updateAttempted(flag: Bool)
}
class StickySheetHostingController<Content>: UIHostingController<Content>, UIAdaptivePresentationControllerDelegate where Content: View {
var shouldDismiss: Bool
var stickySheetDelegate: StickySheetDelegate?
init(rootView: Content, shouldDimiss: Bool) {
self.shouldDismiss = shouldDimiss
super.init(rootView: rootView)
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
viewControllerToPresent.presentationController?.delegate = self
self.stickySheetDelegate?.updateAttempted(flag: false)
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
stickySheetDelegate?.updateAttempted(flag: true)
}
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return shouldDismiss
}
}
struct StickySheetModifier<Item, DestinationView>: ViewModifier where Item: Identifiable, DestinationView: View {
@State var shouldDismiss = false
@State var sheetState = StickySheetState()
@Binding var item: Item?
var destination: (Item) -> DestinationView
var onDismiss: (() -> Void)?
init(item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
@ViewBuilder destination: @escaping (Item) -> DestinationView) {
self._item = item
self.destination = destination
self.onDismiss = onDismiss
}
func body(content: Content) -> some View {
StickySheetWrapper(shouldDismiss: $shouldDismiss,
attempted: $sheetState.dismissAttempted) {
content
.sheet(item: self.$item, onDismiss: self.onDismiss) {
self.destination($0)
.onPreferenceChange(StickySheetShouldDismissPreferenceKey.self) { value in
self.$shouldDismiss.wrappedValue = value
}
.environmentObject(self.sheetState)
}
}
}
}
public extension View {
func shouldDismiss(_ value: Bool) -> some View {
return preference(key: StickySheetShouldDismissPreferenceKey.self, value: value)
}
func stickySheet<Item: Identifiable, DestinationView: View>(item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
@ViewBuilder destination: @escaping (Item) -> DestinationView) -> some View {
modifier(StickySheetModifier(item: item, onDismiss: onDismiss, destination: destination))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment