Skip to content

Instantly share code, notes, and snippets.

@mdb1
Created May 3, 2025 15:25
Show Gist options
  • Save mdb1/f41a7b677af6e8b00e47cfa35136bd22 to your computer and use it in GitHub Desktop.
Save mdb1/f41a7b677af6e8b00e47cfa35136bd22 to your computer and use it in GitHub Desktop.
Documentation for Sheets usage in SwiftUI + a modifier for the close button in the navigation bar
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()
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment