# SwiftUI Sheet, Navigation & Inspector Patterns Reference

## Table of Contents

- [Sheet Patterns](#sheet-patterns)
- [Navigation Patterns](#navigation-patterns)
- [Multi-Column Navigation with NavigationSplitView](#multi-column-navigation-with-navigationsplitview)
- [Inspector](#inspector)
- [Presentation Modifiers](#presentation-modifiers)
- [Summary Checklist](#summary-checklist)

## Sheet Patterns

### Item-Driven Sheets (Preferred)

**Use `.sheet(item:)` instead of `.sheet(isPresented:)` when presenting model-based content.**

```swift
// Good - item-driven
@State private var selectedItem: Item?

var body: some View {
    List(items) { item in
        Button(item.name) {
            selectedItem = item
        }
    }
    .sheet(item: $selectedItem) { item in
        ItemDetailSheet(item: item)
    }
}

// Avoid - boolean flag requires separate state
@State private var showSheet = false
@State private var selectedItem: Item?

var body: some View {
    List(items) { item in
        Button(item.name) {
            selectedItem = item
            showSheet = true
        }
    }
    .sheet(isPresented: $showSheet) {
        if let selectedItem {
            ItemDetailSheet(item: selectedItem)
        }
    }
}
```

**Why**: `.sheet(item:)` automatically handles presentation state and avoids optional unwrapping in the sheet body.

### Sheets Own Their Actions

**Sheets should handle their own dismiss and actions internally** using `@Environment(\.dismiss)`. Avoid passing `onSave`/`onCancel` closures from the parent -- it creates callback prop-drilling and reduces reusability.

```swift
struct EditItemSheet: View {
    @Environment(\.dismiss) private var dismiss
    let item: Item
    @State private var name: String

    init(item: Item) {
        self.item = item
        _name = State(initialValue: item.name)
    }

    var body: some View {
        NavigationStack {
            Form { TextField("Name", text: $name) }
                .navigationTitle("Edit Item")
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } }
                    ToolbarItem(placement: .confirmationAction) { Button("Save") { /* save and dismiss */ } }
                }
        }
    }
}
```

### Enum-Based Sheet Management

When presenting multiple different sheets, use an `Identifiable` enum with `.sheet(item:)` instead of multiple boolean state properties:

```swift
struct ArticlesView: View {
    enum Sheet: Identifiable {
        case add, edit(Article), categories
        var id: String {
            switch self {
            case .add: "add"
            case .edit(let a): "edit-\(a.id)"
            case .categories: "categories"
            }
        }
    }

    @State private var presentedSheet: Sheet?

    var body: some View {
        List { /* ... */ }
            .toolbar {
                Button("Add") { presentedSheet = .add }
            }
            .sheet(item: $presentedSheet) { sheet in
                switch sheet {
                case .add: AddArticleView()
                case .edit(let article): EditArticleView(article: article)
                case .categories: CategoriesView()
                }
            }
    }
}
```

**Why**: A single `@State` property and one `.sheet(item:)` modifier replaces N boolean properties and N sheet modifiers, improving readability and preventing only-one-sheet-at-a-time conflicts.

## Navigation Patterns

### Type-Safe Navigation with NavigationStack

```swift
struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("Profile", value: Route.profile)
                NavigationLink("Settings", value: Route.settings)
            }
            .navigationDestination(for: Route.self) { route in
                switch route {
                case .profile:
                    ProfileView()
                case .settings:
                    SettingsView()
                }
            }
        }
    }
}

enum Route: Hashable {
    case profile
    case settings
}
```

### Programmatic Navigation

```swift
struct ContentView: View {
    @State private var navigationPath = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $navigationPath) {
            List {
                Button("Go to Detail") {
                    navigationPath.append(DetailRoute.item(id: 1))
                }
            }
            .navigationDestination(for: DetailRoute.self) { route in
                switch route {
                case .item(let id):
                    ItemDetailView(id: id)
                }
            }
        }
    }
}

enum DetailRoute: Hashable {
    case item(id: Int)
}
```

## Multi-Column Navigation with NavigationSplitView

### Two-Column Layout

Use `NavigationSplitView` for sidebar-driven navigation. Available on iOS 16+, macOS 13+, tvOS 16+, watchOS 9+.

```swift
struct ContentView: View {
    @State private var selectedItem: Item.ID?

    var body: some View {
        NavigationSplitView {
            List(items, selection: $selectedItem) { item in
                Text(item.name)
            }
            .navigationTitle("Items")
        } detail: {
            if let selectedItem, let item = items.first(where: { $0.id == selectedItem }) {
                ItemDetailView(item: item)
            } else {
                ContentUnavailableView("Select an Item", systemImage: "doc")
            }
        }
    }
}
```

### Three-Column Layout

```swift
struct ContentView: View {
    @State private var departmentId: Department.ID?
    @State private var employeeIds = Set<Employee.ID>()

    var body: some View {
        NavigationSplitView {
            List(model.departments, selection: $departmentId) { dept in
                Text(dept.name)
            }
        } content: {
            if let department = model.department(id: departmentId) {
                List(department.employees, selection: $employeeIds) { emp in
                    Text(emp.name)
                }
            } else {
                Text("Select a department")
            }
        } detail: {
            EmployeeDetails(for: employeeIds)
        }
    }
}
```

### Configuration

- **Column visibility**: `NavigationSplitView(columnVisibility: $visibility)` with `NavigationSplitViewVisibility` (`.detailOnly`, `.doubleColumn`, `.all`)
- **Column widths**: `.navigationSplitViewColumnWidth(min:ideal:max:)` on each column
- **Compact column**: `NavigationSplitView(preferredCompactColumn: $column)` to control which column shows on narrow devices
- **Style**: `.navigationSplitViewStyle(.balanced)` or `.prominentDetail` (default)

### Platform Behavior

| Platform | Behavior |
|----------|----------|
| **macOS** | Columns always visible side-by-side; sidebar has translucent material; variable-width column resizing by dragging |
| **iPadOS (regular)** | Sidebar can overlay or push detail; supports column visibility toggle via toolbar button |
| **iOS / iPadOS (compact)** | Collapses into a single `NavigationStack`; sidebar items show disclosure chevrons; back button navigates between columns |
| **iPhone (all sizes)** | Always collapsed into a stack; sidebar appears as the root list; selections push detail onto the stack |
| **watchOS / tvOS** | Collapses into a single stack |

## Inspector

> **Availability:** iOS 17.0+, macOS 14.0+

A trailing-edge panel for supplementary information.

On wider size classes (macOS, iPad landscape), it appears as a **trailing column**. On compact size classes (iPhone), it **adapts to a sheet** automatically.

### Basic Inspector

```swift
struct ShapeEditor: View {
    @State private var showInspector = false

    var body: some View {
        MyEditorView()
            .inspector(isPresented: $showInspector) {
                InspectorContent()
            }
            .toolbar {
                ToolbarItem {
                    Button {
                        showInspector.toggle()
                    } label: {
                        Label("Inspector", systemImage: "info.circle")
                    }
                }
            }
    }
}
```

### Inspector with Column Width

```swift
MyEditorView()
    .inspector(isPresented: $showInspector) {
        InspectorContent()
            .inspectorColumnWidth(min: 200, ideal: 250, max: 400)
    }
```

### Inspector with Fixed Width

```swift
MyEditorView()
    .inspector(isPresented: $showInspector) {
        InspectorContent()
            .inspectorColumnWidth(300)
    }
```

### Platform Behavior

| Platform | Behavior |
|----------|----------|
| **macOS** | Trailing-edge sidebar panel; resizable by dragging edge; integrates with window toolbar |
| **iPadOS (regular)** | Trailing column alongside content; toggleable via toolbar button |
| **iOS / iPadOS (compact)** | Adapts to a sheet presentation; swipe-to-dismiss supported |
| **iPhone (all sizes)** | Always presented as a sheet (no trailing column); dismiss via swipe or button |

> **Tip:** Use `InspectorCommands` in your app's `.commands` to include the default inspector toggle keyboard shortcut.

## Presentation Modifiers

### Full Screen Cover

```swift
struct ContentView: View {
    @State private var showFullScreen = false
    
    var body: some View {
        Button("Show Full Screen") {
            showFullScreen = true
        }
        .fullScreenCover(isPresented: $showFullScreen) {
            FullScreenView()
        }
    }
}
```

### Popover

```swift
struct ContentView: View {
    @State private var showPopover = false
    
    var body: some View {
        Button("Show Popover") {
            showPopover = true
        }
        .popover(isPresented: $showPopover) {
            PopoverContentView()
                .presentationCompactAdaptation(.popover)  // Don't adapt to sheet on iPhone
        }
    }
}
```

For `alert` and `confirmationDialog` API patterns, see `latest-apis.md`.

## Summary Checklist

- [ ] Use `.sheet(item:)` for model-based sheets
- [ ] Sheets own their actions and dismiss internally
- [ ] Use `NavigationStack` with `navigationDestination(for:)` for type-safe navigation
- [ ] Use `NavigationPath` for programmatic navigation
- [ ] Use `NavigationSplitView` for sidebar-driven multi-column layouts
- [ ] Use `Inspector` for trailing-edge supplementary panels
- [ ] Set column widths with `navigationSplitViewColumnWidth(min:ideal:max:)` or `inspectorColumnWidth(min:ideal:max:)`
- [ ] Use appropriate presentation modifiers (sheet, fullScreenCover, popover)
- [ ] Alerts and confirmation dialogs use modern API with actions
- [ ] Avoid passing dismiss/save callbacks to sheets
- [ ] Use enum-based `Identifiable` type with `.sheet(item:)` when presenting multiple sheets
- [ ] Navigation state can be saved/restored when needed
