Show and hide SwiftUI inspectors with an identifiable item

Sponsored
RevenueCat logo
Releases so easy your work will never pile up

Runway handles the release coordination and busywork so you can focus on building great apps. You do the building, we'll do the shipping.

SwiftUI has a powerful API that allows you to insert a side panel view that shows alongside the view it’s attached to called inspector.

The API is available through the inspector view modifier that takes a binding to a boolean that controls the visibility of the inspector and a closure that returns the view that should be shown in the inspector.

This is a pattern that is widely used in the Apple ecosystem, feels natural to the user, and it’s a great way to show additional information about a view or a component.

In fact, I have recently used an inspector for a feature I have been building for Helm that allows users to select a specific TestFlight build from a list and see its details in a side panel:

Limitations of the inspector modifier

As you can see in the example above, the visibility of the inspector is controlled by whether an item is selected or not. Initially, I started looking for an overload of the inspector modifier that would allow me to pass a binding with an optional identifiable item instead of a boolean, similar to the way the sheet modifier works:

Sheet.swift
nonisolated
func sheet<Item, Content>(
    item: Binding<Item?>,
    onDismiss: (() -> Void)? = nil,
    @ViewBuilder content: @escaping (Item) -> Content
) -> some View where Item : Identifiable, Content : View

Unfortunately, the inspector modifier has its limitations and, at least for now, it only accepts a boolean binding:

Inspector.swift
nonisolated
func inspector<V>(
    isPresented: Binding<Bool>,
    @ViewBuilder content: () -> V
) -> some View where V : View

A custom view modifier

To improve the developer experience and make the API more flexible, I decided to create a custom view modifier that would allow me to pass an optional identifiable item instead of a boolean:

InspectorViewModifier.swift
import SwiftUI

struct InspectorViewModifier<Item: Equatable, InspectorView: View>: ViewModifier {
    @Binding var item: Item?
    @ViewBuilder var inspectorContent: (Item) -> InspectorView
    
    func body(content: Content) -> some View {
        content
            .inspector(isPresented: _item.map(to: { $0 != nil }, from: { _ in item })) {
                item.map(inspectorContent)
            }
    }
}

extension View {
    func inspector<Item: Equatable, InspectorContent: View>(item: Binding<Item?>, @ViewBuilder content: @escaping (Item) -> InspectorContent) -> some View {
        self.modifier(InspectorViewModifier(item: item, inspectorContent: content))
    }
}

extension Binding {
    func map<T>(to: @escaping (Value) -> T, from: @escaping (T) -> Value) -> Binding<T> {
        Binding<T>(
            get: { to(self.wrappedValue) },
            set: { (value: T) in self.wrappedValue = from(value) }
        )
    }
}

The view modifier takes in two parameters: a binding to an optional identifiable and equatable item that will control the visibility of the inspector and a closure that returns the inspector view to render.

The modifier then maps the item binding to a boolean binding that is passed to the inspector modifier.

While this solution works well for my use case, I would love to see Apple provide first-party support for this pattern in the future, so I have filed a feedback request (FB14177256).