SwiftUI Inspector: The Complete Guide to the .inspector() Modifier on iPad, Mac, and visionOS (2026)
A practical guide to SwiftUI's .inspector() modifier: trailing panes on iPad and Mac, sheets on iPhone, tear-off panels on visionOS, plus width control, scene persistence, and the bugs I keep hitting in production.
The SwiftUI .inspector(isPresented:content:) modifier presents a trailing-edge detail pane that sits beside your main content on iPad, Mac, and visionOS, and falls back to a sheet on iPhone. It's the right tool when you want a contextual editor (think Pages' formatting panel or Xcode's attributes inspector) without rebuilding the whole layout for each platform. Available since iOS 17 / macOS 14, the API picked up real width control with inspectorColumnWidth and now plays cleanly with the iOS 26 NavigationSplitView sidebar adaptation work.
.inspector(isPresented:content:) shows a trailing pane on regular-width devices (iPad, Mac, visionOS) and a sheet on compact-width (iPhone).
Use inspectorColumnWidth(min:ideal:max:) to make the pane user-resizable on Mac and iPad; a single value pins it.
Inspector composes with NavigationSplitView as a fourth column to the right of the detail view, so order matters.
On visionOS the inspector becomes a separate ornament-style panel that you can tear off, so plan for it not always being attached.
On iPhone the inspector presents as a sheet with default detents; combine with presentationDetents for control.
The inspector keeps its own state, so store the toggle in @SceneStorage if you want it to survive backgrounding and window restore.
What is the SwiftUI inspector modifier?
The inspector is a SwiftUI presentation style introduced in iOS 17 and macOS 14 for showing supplementary detail next to a primary view. Unlike a sheet, it doesn't cover content; unlike a popover, it's anchored to the trailing edge of the window. Think of it as the SwiftUI equivalent of UIKit's UISplitViewController trailing column, or AppKit's NSSplitViewController inspector pane, just with one declarative modifier and the system handling adaptation for you.
In practice, I reach for the inspector whenever a single selection in the main view needs an editing surface that the user wants to keep open while they continue working. Photo metadata, shape attributes, formatting panels, debug overlays in dev tools, code-review comment threads. These are all classic inspector use cases. The contrast with a sheet is that the user doesn't have to dismiss it to keep interacting with the underlying content, which is exactly what makes it the right pattern for "tweak while you work" flows.
Apple's official documentation describes the modifier as inserting a supplementary view, but in real apps the interesting question is always how it behaves at different size classes, and that's what most of this guide covers.
Basic usage: presenting an inspector
So, the signature is straightforward. You pass a binding that controls visibility plus a closure returning the pane's content. Here's the minimal example I'd put in a sample app to verify it works end to end:
Two things to call out. First, the inspector modifier should be applied to the view whose window you want to host the pane in, so apply it to the root or to a column of a NavigationSplitView, not to a row inside a list. Second, the inspector content runs in its own implicit NavigationStack on iOS 17.4 and later, which means navigation titles, search bars, and toolbar items in the pane render in the expected place without extra wrapping. On macOS, the pane uses the window's chrome, so a navigationTitle sets the inspector's section header but not the window title.
How do you control the inspector width and make it resizable?
Use the inspectorColumnWidth modifier inside the inspector's content closure. There are two forms: a single-value form that pins the width, and a min/ideal/max form that gives the user a draggable handle on Mac and iPad with a pointer attached.
// Fixed width — no user resize.
.inspector(isPresented: $showInspector) {
InspectorPane(item: selectedItem)
.inspectorColumnWidth(300)
}
// Resizable: drag handle on Mac and iPad.
.inspector(isPresented: $showInspector) {
InspectorPane(item: selectedItem)
.inspectorColumnWidth(min: 220, ideal: 280, max: 480)
}
A few quirks worth knowing. The ideal width is what the inspector opens to the first time; afterward the system remembers the user's drag position per scene, even across launches, as long as the scene is restored. The min and max are hard limits. Drags clamp at the edges, they don't collapse the pane. To collapse, the user toggles your binding (via your toolbar button or the system's built-in show/hide inspector control on Mac).
On iPhone the modifier is ignored because the inspector presents as a sheet, where width is determined by detents instead. If you want compact-width control too, combine with presentationDetents as I show in the cross-platform section below.
Combining inspector with NavigationSplitView
The most common shipped layout I see in production iPad apps is a three-column NavigationSplitView plus an inspector: sidebar, content, detail, inspector. The trick is that the inspector modifier must be applied at the root of the split view, not on the detail column. Otherwise it gets scoped to a single column's pop stack and disappears when you change selections (I hit this exact bug shipping a notes app last year and spent an embarrassing afternoon on it).
On a 13-inch iPad in landscape this renders all four columns simultaneously. On an 11-inch iPad in landscape, the system collapses the sidebar into a swipe-out drawer when the inspector opens, because four columns won't fit. On a Mac you get all four columns and a beautifully resizable layout. On iPhone the whole thing collapses to a NavigationStack with the inspector arriving as a sheet, which is exactly the cross-platform payoff this API is selling. For a deeper look at how the underlying split view adapts, our NavigationSplitView guide covers column visibility, sidebar toggle behavior, and the iOS 26 sidebar adaptation changes that also affect inspector layout.
How does the inspector behave on iPad, Mac, iPhone, and visionOS?
Honestly, this is the section I wish existed when I first shipped an inspector-using app to all four platforms. The behavior diverges in ways that matter for layout testing:
Platform
Default presentation
Resizable
Notes
iPad (regular width)
Trailing column
Yes (drag handle)
Width persists per scene; collapses sidebar at 11" landscape if needed.
iPad (compact width)
Sheet with medium detent
No
Slide-over and iPhone-like multitasking modes trigger this.
Mac
Trailing pane in window
Yes (drag handle)
Hosts in window chrome; navigationTitle sets pane header.
Mac Catalyst
Trailing column (iPad-style)
Yes
Width persists via state restoration when "Restore windows" is on.
iPhone
Sheet, large detent by default
Via presentationDetents
Use presentationDetents in the inspector content to control.
visionOS
Attached panel (tear-off capable)
System-managed
User can detach pane into its own window via the chrome handle.
watchOS / tvOS
Not supported
n/a
Modifier compiles but has no effect; conditionally hide the toggle.
On iPhone, layering presentationDetents on the inspector's root view gives you the same detent control you'd use for any sheet. Most apps want a .medium + .large pairing with a grabber:
On visionOS, the user can grab the pane's chrome and pull it out into its own floating window. Your inspector closure can still observe the binding. When the user tears off the pane, your binding stays true until they explicitly close the floating window. That can surprise you if your toolbar button label depends on the binding ("Hide Inspector" while the pane is floating beside the window can look wrong). I usually just label the button "Inspector" and leave it.
Sheet vs inspector vs popover: which one should you use?
This trips up almost everyone moving from UIKit. The three SwiftUI presentation modifiers serve different jobs, and the answer isn't "whichever looks nicest":
Sheet (.sheet): modal task that interrupts the main flow until the user finishes or cancels. Compose, share, sign in, edit-then-save. The user cannot interact with the underlying content while it's up.
Inspector (.inspector): persistent companion pane showing attributes of the current selection. The user expects to keep editing the main content with the pane open.
Popover (.popover): small contextual control anchored to a source view. Quick picker, color choice, brief info card. Dismisses on outside tap.
A useful test: if the user might want both views simultaneously editable, you want an inspector. If completing the secondary view should return them to the main one, you want a sheet. If it's a short tap that pops up near a button, you want a popover. On iPhone, the inspector falls back to a sheet anyway, so the distinction can blur, but the intent of your code stays clearer when you pick correctly.
Persisting inspector state across launches
The visibility binding is just a Bool, so you choose where to store it. For app-wide preference, use @AppStorage; for per-scene state that should survive backgrounding and state restoration but reset between fresh installs, use @SceneStorage. The latter is what Apple's own apps do:
struct EditorScene: View {
@SceneStorage("inspector.visible") private var showInspector = true
@State private var item: Item? = nil
var body: some View {
EditorRoot(item: $item)
.inspector(isPresented: $showInspector) {
InspectorPane(item: item)
.inspectorColumnWidth(min: 220, ideal: 280, max: 420)
}
}
}
The inspector's width is persisted automatically by the framework, so you don't need to wire that yourself. What you do need to handle is the visibility toggle, because the system won't assume your default. On Mac, I default to true in editor-style apps and false in document-browser apps. On iPad, I default to false in compact and true in regular, which you can express with a one-time check on the horizontal size class at scene init.
If your inspector reflects an editable model, hold the model in an @Observable store so the inspector and main view see the same source of truth. See our @Observable macro guide for the patterns I use in production. Passing a bare let item into the pane is fine for read-only metadata, but the moment the inspector edits properties you want shared state, not a value copy.
Common pitfalls and how to fix them
Five inspector bugs I've hit (or watched colleagues hit) more than once:
Inspector disappears on selection change
You applied .inspector to the detail column inside a NavigationSplitView. The detail column rebuilds when the selection changes, blowing away the inspector along with it. Apply the modifier to the split view root.
Inspector content has no toolbar
Toolbar items inside the inspector pane render in the inspector's implicit NavigationStack, but only if the pane has a navigationTitle or you wrap the body in a NavigationStack { } explicitly. Add a title (even a hidden one with .toolbarTitleDisplayMode(.inline) and an empty string) and the toolbar slot appears.
Inspector ignores width settings on iPhone
By design: on compact width it's a sheet. Add presentationDetents instead. If you want it to look like a "real" inspector on a Plus or Max in landscape, those are still compact horizontally, so SwiftUI gives you the sheet there too. There's no override.
Inspector "blinks" during scene restore
This happens when you bind visibility to @State instead of @SceneStorage. The state restores after the first frame, so the pane animates closed and back open. Move to @SceneStorage as shown above.
Inspector overlaps content on visionOS
If you have ornaments attached to the same window, the inspector can collide with their hit-testing region. The fix is to give your ornaments explicit .ornamentAnchor placements away from the trailing edge, or to detach the inspector visually by letting users tear it off.
Frequently Asked Questions
Does the SwiftUI inspector work on iPhone?
Yes, but it adapts to a sheet. On compact-width devices, including all iPhones and iPads in slide-over, the inspector content is presented as a modal sheet using the standard detents. The inspectorColumnWidth modifier is ignored, so use presentationDetents to control sheet height.
What's the minimum iOS version that supports the inspector modifier?
iOS 17.0 and macOS 14.0 (Sonoma). The inspectorColumnWidth overloads also require iOS 17 / macOS 14. On earlier OS versions the modifier is unavailable, so you need #available checks or to gate the inspector feature behind a deployment-target bump.
How do I toggle the SwiftUI inspector programmatically?
The visibility binding is the only handle. Flip the bound Bool from a button, menu command, or keyboard shortcut. There's no environment value to dismiss the inspector from inside its own content, so pass the binding in if you need a "Close" button in the pane.
Can I have more than one inspector in a view hierarchy?
Technically yes (nested inspectors compose), but the user experience is poor on every platform. SwiftUI will stack them as columns on Mac, but on iPad and iPhone only the outermost will be visible. Stick to one inspector per scene and switch its content based on selection state.
Why does my inspector show no toolbar buttons?
The inspector's implicit navigation chrome only appears when its root content has a navigationTitle. Add a title (even an empty one with inline display mode) and your ToolbarItems inside the pane will render. Without a title, the chrome collapses and toolbar items go nowhere.
How to use SwiftUI PhotosPicker in iOS 26 for multi-select, video filtering, and file-based loading. Covers race conditions, iCloud progress, and VoiceOver.
Swift RegexBuilder gives you type-safe regular expressions with named captures, inline transforms, and Unicode-correct matching by default. Learn the DSL, compare it to NSRegularExpression, and avoid production traps.
Build SwiftUI Metal shaders in iOS 26 with colorEffect, distortionEffect, and layerEffect. Includes MSL setup, TimelineView animation, and GPU cost tips with working code.