Skip to content

Instantly share code, notes, and snippets.

@mdb1
Created May 3, 2025 15:25
Show Gist options
  • Select an option

  • Save mdb1/f41a7b677af6e8b00e47cfa35136bd22 to your computer and use it in GitHub Desktop.

Select an option

Save mdb1/f41a7b677af6e8b00e47cfa35136bd22 to your computer and use it in GitHub Desktop.

Revisions

  1. mdb1 created this gist May 3, 2025.
    107 changes: 107 additions & 0 deletions Sheets.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,107 @@
    import SwiftUI

    extension View {
    /// For sheets, we will follow the SwiftUI built-in approach:
    ///
    /// If we only need to present one possible sheet, we can use a Boolean binding and use `.sheet(isPresented:)`
    ///
    /// `.sheet(isPresented:)`
    /// ===================================
    /// ```swift
    /// struct SomeScreen: View {
    /// @State private var isPresentingSheet: Bool = false
    ///
    /// var body: some View {
    /// Button("Present Sheet") {
    /// isPresentingSheet = true
    /// }
    /// .sheet(isPresented: $isPresentingSheet) {
    /// Text("Presented sheet")
    /// }
    /// }
    /// }
    /// ```
    ///
    /// In case there is a screen that can present multiple type of sheets, we can use enum-based presentation:
    ///
    /// `sheet(item:)`
    /// ===================================
    /// ```swift
    /// struct SomeScreen: View {
    /// enum Sheet: String, Identifiable {
    /// case firstPresentedScreen, secondScreen, thirdScreen
    /// var id: String { rawValue }
    /// }
    ///
    /// @State private var presentedSheet: Sheet?
    ///
    /// var body: some View {
    /// VStack {
    /// Button("Present Sheet") {
    /// presentedSheet = .firstPresentedScreen
    /// }
    /// Button("Present Second Sheet") {
    /// presentedSheet = .secondScreen
    /// }
    /// Button("Present Third Sheet") {
    /// presentedSheet = .thirdScreen
    /// }
    /// }
    /// .sheet(item: $presentedSheet) { sheet in
    /// switch sheet {
    /// case .firstPresentedScreen:
    /// Text("First presented screen")
    /// case .secondScreen:
    /// Text("Second screen")
    /// case .thirdScreen:
    /// Text("Third screen")
    /// }
    /// }
    /// }
    /// }
    /// ```
    ///
    /// Detents / DragIndicator / CloseButton
    /// ===================================
    /// If we need to display different detents, we can just use the `.detents` modifier:
    /// ```swift
    /// struct SomeScreen: View {
    /// @State private var isPresentingSheet: Bool = false
    ///
    /// var body: some View {
    /// Button("Present Sheet") {
    /// isPresentingSheet = true
    /// }
    /// .sheet(isPresented: $isPresentingSheet) {
    /// NavigationStack {
    /// Text("Presented sheet")
    /// .presentationDetents([.medium, .large]) // <---- HERE
    /// .presentationDragIndicator(.visible) // Displays the drag indicator
    /// .withNavigationCloseButton { isPresentingSheet = false }
    /// }
    /// }
    /// }
    /// }
    /// ```
    func withNavigationCloseButton(onTap: @escaping () -> Void) -> some View {
    modifier(CloseModalButtonModifier(onTap: onTap))
    }
    }

    /// A `ViewModifier` that adds a Close button to the navigation bar on the trailing position.
    /// Used within screens that are presented modally.
    struct CloseModalButtonModifier: ViewModifier {
    var onTap: () -> Void

    func body(content: Content) -> some View {
    content
    .toolbar {
    ToolbarItem(placement: .topBarTrailing) {
    // TODO: Replace with your custom Close Button for the Nav Bar
    Button("Close".localized) {
    onTap()
    }
    }
    }
    }
    }