| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- extension View {
- /// Presents a conditional modal overlay. `onDismiss` gets invoked when the
- /// sheet is dismissed.
- ///
- /// On most platforms sheets appear as form-style modals. On tvOS, sheets
- /// appear as full screen overlays (non-opaque).
- ///
- /// `onDismiss` isn't called when the sheet gets dismissed programmatically
- /// (i.e. by setting `isPresented` to `false`).
- ///
- /// `onDismiss` gets called *after* the sheet has been dismissed by the
- /// underlying UI framework, and *before* `isPresented` gets set to false.
- ///
- /// - Parameters:
- /// - isPresented: A binding controlling whether the sheet is presented.
- /// - onDismiss: An action to perform when the sheet is dismissed
- /// by the user.
- /// - content: The content of the sheet
- public func sheet<SheetContent: View>(
- isPresented: Binding<Bool>,
- onDismiss: (() -> Void)? = nil,
- @ViewBuilder content: @escaping () -> SheetContent
- ) -> some View {
- SheetModifier(
- isPresented: isPresented,
- body: TupleView1(self),
- onDismiss: onDismiss,
- sheetContent: content
- )
- }
- }
- struct SheetModifier<Content: View, SheetContent: View>: TypeSafeView {
- typealias Children = SheetModifierViewChildren<Content, SheetContent>
- var isPresented: Binding<Bool>
- var body: TupleView1<Content>
- var onDismiss: (() -> Void)?
- var sheetContent: () -> SheetContent
- var sheet: Any?
- func children<Backend: BaseAppBackend>(
- backend: Backend,
- snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
- environment: EnvironmentValues
- ) -> Children {
- let bodyViewGraphNode = ViewGraphNode(
- for: body.view0,
- backend: backend,
- environment: environment
- )
- let bodyNode = AnyViewGraphNode(bodyViewGraphNode)
- return SheetModifierViewChildren(
- childNode: bodyNode,
- sheetContentNode: nil,
- sheet: nil
- )
- }
- func asWidget<Backend: BaseAppBackend>(
- _ children: Children,
- backend: Backend
- ) -> Backend.Widget {
- children.childNode.widget.into()
- }
- func computeLayout<Backend: BaseAppBackend>(
- _ widget: Backend.Widget,
- children: Children,
- proposedSize: ProposedViewSize,
- environment: EnvironmentValues,
- backend: Backend
- ) -> ViewLayoutResult {
- children.childNode.computeLayout(
- with: body.view0,
- proposedSize: proposedSize,
- environment: environment
- )
- }
- func commit<Backend: BaseAppBackend>(
- _: Backend.Widget,
- children: Children,
- layout: ViewLayoutResult,
- environment: EnvironmentValues,
- backend: Backend
- ) {
- func castBackend_inner<NewBackend: BaseAppBackend & BackendFeatures.Sheets>(_ backend: NewBackend) {
- _ = children.childNode.commit()
- if isPresented.wrappedValue {
- let needsPresenting = children.sheet == nil
- let sheet: NewBackend.Sheet
- if children.sheetContentNode == nil {
- let sheetViewGraphNode = ViewGraphNode(
- for: sheetContent(),
- backend: backend,
- environment: environment
- )
- let sheetContentNode = AnyViewGraphNode(sheetViewGraphNode)
- children.sheetContentNode = sheetContentNode
- sheet = backend.createSheet(
- content: children.sheetContentNode!.widget.into()
- )
- } else {
- guard
- let existingSheet = children.sheet,
- let castedSheet = existingSheet as? NewBackend.Sheet
- else {
- logger.warning(
- """
- SheetModifier has a nil sheet, even though the sheet \
- has already been presented
- """
- )
- return
- }
- sheet = castedSheet
- }
- let dismissAction = DismissAction(action: { [isPresented] in
- isPresented.wrappedValue = false
- })
- let sheetEnvironment =
- environment
- .with(\.dismiss, dismissAction)
- .with(\.sheet, sheet)
- _ = children.sheetContentNode!.computeLayout(
- with: sheetContent(),
- proposedSize: .unspecified,
- environment: sheetEnvironment
- )
- let result = children.sheetContentNode!.commit()
- let window = environment.window!
- let preferences = result.preferences
- backend.updateSheet(
- sheet,
- window: window as! NewBackend.Window,
- // We intentionally use the outer environment rather than
- // sheetEnvironment here, because this is meant to be the sheet's
- // environment, not that of its content.
- environment: environment,
- size: result.size.vector,
- onDismiss: { handleDismiss(children: children) },
- cornerRadius: preferences.presentationCornerRadius,
- detents: preferences.presentationDetents ?? [],
- dragIndicatorVisibility: preferences.presentationDragIndicatorVisibility
- ?? .automatic,
- backgroundColor: preferences.presentationBackground?.resolve(in: environment),
- interactiveDismissDisabled: preferences.interactiveDismissDisabled ?? false
- )
- let parentSheet = environment.sheet.map { $0 as! NewBackend.Sheet }
- if needsPresenting {
- backend.presentSheet(
- sheet,
- window: window as! NewBackend.Window,
- parentSheet: parentSheet
- )
- }
- children.sheet = sheet
- children.window = window
- children.parentSheet = parentSheet
- } else if !isPresented.wrappedValue && children.sheet != nil {
- backend.dismissSheet(
- children.sheet as! NewBackend.Sheet,
- window: children.window! as! NewBackend.Window,
- parentSheet: children.parentSheet.map { $0 as! NewBackend.Sheet }
- )
- children.sheet = nil
- children.window = nil
- children.parentSheet = nil
- children.sheetContentNode = nil
- }
- }
- guard let castedBackend = backend as? any (BaseAppBackend & BackendFeatures.Sheets) else {
- fatalError("Backend does not implement BackendFeatures.Sheets")
- }
- castBackend_inner(castedBackend)
- }
- func handleDismiss(children: Children) {
- onDismiss?()
- children.sheet = nil
- children.window = nil
- children.parentSheet = nil
- children.sheetContentNode = nil
- isPresented.wrappedValue = false
- }
- }
- class SheetModifierViewChildren<Child: View, SheetContent: View>: ViewGraphNodeChildren {
- var widgets: [AnyWidget] {
- [childNode.widget]
- }
- var erasedNodes: [ErasedViewGraphNode] {
- var nodes: [ErasedViewGraphNode] = [ErasedViewGraphNode(wrapping: childNode)]
- if let sheetContentNode {
- nodes.append(ErasedViewGraphNode(wrapping: sheetContentNode))
- }
- return nodes
- }
- var childNode: AnyViewGraphNode<Child>
- var sheetContentNode: AnyViewGraphNode<SheetContent>?
- var sheet: Any?
- var window: Any?
- var parentSheet: Any?
- init(
- childNode: AnyViewGraphNode<Child>,
- sheetContentNode: AnyViewGraphNode<SheetContent>?,
- sheet: Any?
- ) {
- self.childNode = childNode
- self.sheetContentNode = sheetContentNode
- self.sheet = sheet
- }
- }
|