Library / SDK
siteline/swiftui-introspect avatar
siteline/swiftui-introspect

SwiftUI Introspect: the marker walk, the version opt in, and the silent failure

Introspect underlying UIKit/AppKit components from SwiftUI

6,578 stars422 forksSwiftMIT

At a glance

What is it?
SwiftUI Introspect reaches the UIKit or AppKit view behind a SwiftUI view by bracketing it with two invisible markers and walking the hierarchy between them. It avoids private API entirely, and every call needs an explicit platform version list because UIKit and AppKit types can change between major versions.
Who is it for?
Reach for it when you need a UIKit or AppKit object SwiftUI does not surface, such as a scroll view delegate, and you can name the exact OS versions your app supports. Pass on it when you need the library to keep working across an OS release you have not enumerated, or when a silent no-op would leave you debugging a missing callback with no error.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • siteline/swiftui-introspect6,578 ★ · +19
Star history
Repository2026-09-232026-11-14
siteline/swiftui-introspect6.6k6.6k

19 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Two invisible markers bracket the view and a hierarchy walk finds the object

The whole mechanism is a spatial search. SwiftUI Introspect places an invisible `IntrospectionView` above the SwiftUI view you selected and an invisible anchor below it, then walks the UIKit or AppKit hierarchy between the two markers until it finds a view of the type you asked for. For a `ScrollView` the sequence is short: add the markers, traverse every subview in between, stop at the first `UIScrollView`. The library describes the approach as solid and unlikely to break on its own, since it depends on public traversal rather than on Apple's private layout internals.

Every call needs an explicit version list, because UIKit types move between majors

That robustness has a price at the call site. A `.iOS(.v17, .v18, .v26, .v27)` argument is not decoration: the library requires explicit opt in for each OS version because the underlying UIKit and AppKit types can change between major versions. The consequence for a team is bookkeeping. Each `.introspect` call site is a promise that the mapping between the SwiftUI view and its platform counterpart still holds on every version you name, and a yearly OS bump means auditing those promises. The explicit list is inconvenient in a large codebase and honest about what the library can actually guarantee.

Calling introspect on the receiver itself does nothing, so ancestor scope exists

By default `.introspect` acts on its receiver, which means the modifier has to sit on the view whose platform object you want. Attach it to a child inside a `ScrollView` and the marker pair lands in the wrong place, so the walk searches a subtree that never contains the `UIScrollView`. The failure is silent. The fix is `scope: .ancestor`, which moves the search up so a nested view can reach the ancestor it actually cares about. It is worth checking before assuming a view type is unimplemented. It also means nesting order in your SwiftUI code is part of the contract: rearranging a hierarchy can silently move which view a callback receives.

A miss is ignored on purpose, so nothing throws when the hierarchy shifts

The defensive posture is explicit in how failure is handled. The library inspects the hierarchy through public methods, makes no hard layout assumptions, performs no forced casts to UIKit or AppKit classes, and ignores `.introspect` entirely when the expected view cannot be found. There is no error, no log, no return value. That is a deliberate trade: the alternative, crashing or force casting, is worse in production. The cost lands on you. If the closure never runs, the only evidence is the thing you were trying to set up not happening, and nothing in the library tells you the search came back empty. Combined with the receiver default, that gives you two independent silent no-ops to rule out before you suspect an unsupported view type.

Each supported view and each style is a separate type to import

Coverage is enumerated one entry at a time rather than by generic introspection, which is why the list reads like an inventory. `Button`, `ColorPicker`, `DatePicker`, `Form`, `List`, `ListCell`, `Map`, `NavigationSplitView`, `NavigationStack`, `PageControl` are covered, along with `.fullScreenCover` and `.popover`. Styles get their own entries rather than being folded in: `DatePicker` alone has `.compact`, `.field`, `.graphical`, `.stepperField` and `.wheel`, `List` has `.bordered`, `.grouped`, `.insetGrouped`, `.inset` and `.sidebar`, and `Form`, `NavigationView` and `Picker` each carry their own style variants. Each name resolves to a distinct type, so adding a style you use means importing and naming another one. For anything not on the list, the README's Advanced usage section covers implementing your own introspectable type.

Adding it means two Swift Package Manager lines, pinned from 27.0.0

Installation is a Swift Package Manager dependency and one product entry. Declare the package in your dependency list:

swift
.package(url: "https://github.com/siteline/swiftui-introspect", from: "27.0.0"),

Then add the product to your target:

swift
.product(name: "SwiftUIIntrospect", package: "swiftui-introspect"),

The `from` value is the version floor; the newest release is 27.1.0, published on 2026-10-07. Because the library tracks OS versions rather than SwiftUI features, the more important first use is not the import but the call site: write the version list that covers every OS your deployment target allows, then verify on a device for each one.

Repository layout pairs a package with a separate example showcase and agent instructions

The top level is a Swift package with tooling around it. `Package.swift`, `Sources/` and `Tests/` are the package itself, `SwiftUIIntrospect.xcworkspace/` is the workspace used for development, and `Examples/` is a second package with its own `Package.swift`, a `Showcase/` target and a `.swiftpm/` directory, so the usage examples are built and runnable rather than prose. Tooling is committed too: `.swift-version` pins the toolchain, `.swiftformat` and `.editorconfig` fix formatting, `mise.toml` and `mise.lock` pin the tool versions, `hk.pkl` is committed alongside them, and `AGENTS.md` carries instructions for coding agents working in the tree. Licence is MIT.

Release numbering follows the OS generation, and 26.1.0 and 27.0.0 landed the same day

The version scheme is tied to the platform generation rather than to the library's own feature count, which makes release notes easy to misread. The feed shows 1.5.0, 26.2.0 and 27.1.0 all published on 2026-10-07 between 09:28:43Z and 09:29:58Z, so several release lines ship together. The repository is not archived and the last push was on 2026-10-07, so expect the version list in your code to need revisiting each year.

Editorial conclusion

Reach for it when you need a UIKit or AppKit object SwiftUI does not surface, such as a scroll view delegate, and you can name the exact OS versions your app supports. Pass on it when you need the library to keep working across an OS release you have not enumerated, or when a silent no-op would leave you debugging a missing callback with no error. Before shipping, run your introspected code paths on every version in your opt in list, and check whether a public SwiftUI API has caught up with the workaround.

Frequently asked questions

Does SwiftUI Introspect use private API?

No. It inspects the view hierarchy using public methods, makes no hard layout assumptions, performs no forced casts to UIKit or AppKit classes, and ignores the call when the expected view cannot be found.

Why does my SwiftUI Introspect closure never run?

Two causes are most likely. By default `.introspect` acts on its receiver, so a modifier placed on a child inside the view you want will search the wrong subtree and needs `scope: .ancestor`. The library also ignores the call silently when no matching UIKit or AppKit view exists.

Which SwiftUI views can SwiftUI Introspect reach?

Among others `Button`, `ColorPicker`, `DatePicker`, `Form`, `List`, `ListCell`, `Map`, `NavigationSplitView`, `NavigationStack`, `PageControl`, `.fullScreenCover` and `.popover`. Styles are listed separately, for example `List` with `.insetGrouped` and `DatePicker` with `.wheel`.

How do I install SwiftUI Introspect?

Add the package from version 27.0.0 to your Swift Package Manager dependencies and add the `SwiftUIIntrospect` product to your target.

Do I have to opt in to every iOS version for SwiftUI Introspect to work?

Yes. A version list such as `.iOS(.v17, .v18, .v26, .v27)` is required, because underlying UIKit and AppKit types can change between major versions and each release needs explicit opt in.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. siteline/swiftui-introspect on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/siteline-swiftui-introspect.svg)](https://hysenlabs.com/projects/siteline-swiftui-introspect)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/siteline-swiftui-introspect.svg)](https://hysenlabs.com/projects/siteline-swiftui-introspect)

Was this useful?