You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+18-22Lines changed: 18 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,9 +5,7 @@ SwiftUI Introspect
5
5
[](https://swiftpackageindex.com/siteline/swiftui-introspect)
SwiftUI Introspect allows you to get the underlying UIKit or AppKit element of a SwiftUI view.
9
-
10
-
For instance, with SwiftUI Introspect you can access `UITableView` to modify separators, or `UINavigationController` to customize the tab bar.
8
+
SwiftUI Introspect lets you access the underlying UIKit or AppKit view for a SwiftUI view.
11
9
12
10
-[How it works](#how-it-works)
13
11
-[Install](#install)
@@ -26,7 +24,7 @@ For instance, with SwiftUI Introspect you can access `UITableView` to modify sep
26
24
How it works
27
25
------------
28
26
29
-
SwiftUI Introspect works by adding an invisible `IntrospectionView`on top of the selected view, and an invisible "anchor" view underneath it, then looking through the UIKit/AppKit view hierarchy between the two to find the relevant view.
27
+
SwiftUI Introspect adds an invisible `IntrospectionView`above the selected view and an invisible anchor below it, then searches the UIKit/AppKit view hierarchy between them to find the relevant view.
30
28
31
29
For instance, when introspecting a `ScrollView`...
32
30
@@ -41,13 +39,13 @@ ScrollView {
41
39
42
40
... it will:
43
41
44
-
1. Add marker views in front and behind`ScrollView`.
42
+
1. Add marker views before and after`ScrollView`.
45
43
2. Traverse through all subviews between both marker views until a `UIScrollView` instance (if any) is found.
46
44
47
45
> [!IMPORTANT]
48
-
> Although this introspection method is very solid and unlikely to break in itself, future OS releases require explicit opt-in for introspection (`.iOS(.vXYZ)`), given potential differences in underlying UIKit/AppKit view types between major OS versions.
46
+
> Although this method is solid and unlikely to break on its own, future OS releases require explicit optin for introspection (`.iOS(.vXYZ)`) because underlying UIKit/AppKit types can change between major versions.
49
47
50
-
By default, the `.introspect`modifier acts directly on its _receiver_. This means calling `.introspect` from inside the view you're trying to introspect won't have any effect. However, there are times when this is not possible or simply too inflexible, in which case you **can**introspect an _ancestor_, but you must opt into this explicitly by overriding the introspection `scope`:
48
+
By default, `.introspect` acts on its receiver. Calling `.introspect` from inside the view you want to introspect has no effect. If you need to introspect an ancestor instead, set `scope: .ancestor`:
51
49
52
50
```swift
53
51
ScrollView {
@@ -60,7 +58,7 @@ ScrollView {
60
58
61
59
### Usage in production
62
60
63
-
SwiftUI Introspect is meant to be used in production. It does not use any private API. It only inspects the view hierarchy using publicly available methods. The library takes a defensive approach to inspecting the view hierarchy: there is no hard assumption that elements are laid out a certain way, there is no force-cast to UIKit/AppKit classes, and the`.introspect`modifier is simply ignored if UIKit/AppKit views cannot be found.
61
+
SwiftUI Introspect is suitable for production. It does not use private APIs. It inspects the view hierarchy using public methods and takes a defensive approach: it makes no hard layout assumptions, performs no forced casts to UIKit/AppKit classes, and ignores`.introspect`when the expected UIKit/AppKit view cannot be found.
64
62
65
63
Install
66
64
-------
@@ -225,18 +223,18 @@ General Guidelines
225
223
226
224
Here are some guidelines to keep in mind when using SwiftUI Introspect:
227
225
228
-
-**Use sparingly**: introspection should be a last resort when you need to access underlying UIKit/AppKit components that SwiftUI does not expose. Overusing it can lead to fragile code that may break with future SwiftUI updates. As Apple introduces new modifiers to SwiftUI, consider replacing introspection with native SwiftUI solutions.
226
+
-**Use sparingly**: prefer native SwiftUI modifiers when available. Use introspection only when you need underlying UIKit/AppKit APIs that SwiftUI does not expose.
229
227
-**Program defensively**: the introspection closure may be called multiple times during the view's lifecycle, such as during view updates or re-renders. Ensure that your customization code can handle being executed multiple times without causing unintended side effects.
230
-
-**Do not modify state directly**: avoid changing SwiftUI state directly from within the introspection closure. If you need to update state, enclose it within a `DispatchQueue.main.async` block to ensure it happens within safe SwiftUI update cycles.
231
-
-**Test on all target OS versions**: since SwiftUI Introspect relies on the underlying view hierarchy, it's crucial to test your app on all the OS versions you intend to support. Different OS versions may have different underlying implementations, which can affect introspection.
228
+
-**Avoid direct state changes**: do not change SwiftUI state from inside the introspection closure. If you must update state, wrap it in `DispatchQueue.main.async`.
229
+
-**Test across OS versions**: underlying implementations can differ by OS, which can affect customization.
232
230
-**Avoid retain cycles**: be cautious about capturing `self` or other strong references within the introspection closure, as this can lead to memory leaks. Use `[weak self]` or `[unowned self]` capture lists as appropriate.
233
-
-**Use the correct scope**: by default, the `.introspect`modifier acts on its receiver. If you need to introspect an ancestor view, make sure to set the `scope` parameter to `.ancestor`. In general, you won't need to worry about this as each view type has sensible, predictable scope defaults.
231
+
-**Scope**: `.introspect`targets its receiver by default. Use `scope: .ancestor` only when you need to introspect an ancestor. In general, you shouldn't worry about this as each view type has sensible, predictable default scopes.
234
232
235
233
Advanced usage
236
234
--------------
237
235
238
236
> [!NOTE]
239
-
> The following features are considered advanced and are not necessary for most use cases. They are provided for users who need more control or flexibility when using SwiftUI Introspect.
237
+
> These features are advanced and unnecessary for most use cases. Use them when you need extra control or flexibility.
240
238
241
239
> [!IMPORTANT]
242
240
> To access these features, import SwiftUI Introspect using `@_spi(Advanced)` (see examples below).
@@ -245,7 +243,7 @@ Advanced usage
245
243
246
244
**Missing an element?** Please [start a discussion](https://github.com/siteline/swiftui-introspect/discussions/new?category=ideas).
247
245
248
-
In case SwiftUI Introspect (unlikely) doesn't support the SwiftUI element that you're looking for, you can implement your own introspectable type.
246
+
In the unlikely event SwiftUI Introspect does not support the element you need, you can implement your own introspectable type.
249
247
250
248
For example, here's how the library implements the introspectable `TextField` type:
By default, introspection applies per specific platform version. This is a sensible default for maximum predictability in regularly maintained codebases, but it's not always a good fit for e.g. library developers who may want to cover as many future platform versions as possible in order to provide the best chance for long-term future functionality of their library without regular maintenance.
304
-
305
-
For such cases, SwiftUI Introspect offers range-based platform version predicates behind the Advanced SPI:
301
+
By default, introspection targets specific platform versions. This is an intentional design decision to maintain maximum predictability in actively maintained apps. However library authors may prefer to cover future versions to limit their commitment to regular maintenance without breaking client apps. For that, SwiftUI Introspect provides range-based version predicates via the Advanced SPI:
306
302
307
303
```swift
308
304
importSwiftUI
@@ -320,11 +316,11 @@ struct ContentView: View {
320
316
}
321
317
```
322
318
323
-
Bear in mind this should be used cautiously, and with full knowledge that any future OS version might break the expected introspection types unless explicitly available. For instance, if in the example above hypothetically iOS 19 stops using UIScrollView under the hood, the customization closure will never be called on said platform.
319
+
Use this cautiously. Future OS versions may change underlying types, in which case the customization closure will not run unless support is explicitly declared.
324
320
325
321
### Keep instances outside the customize closure
326
322
327
-
Sometimes, you might need to keep your introspected instance around for longer than the customization closure lifetime. In such cases, `@State` is not a good option because it produces retain cycles. Instead, SwiftUI Introspect offers a `@Weak` property wrapper behind the Advanced SPI:
323
+
Sometimes you need to keep an introspected instance beyond the customization closure. `@State` is not appropriate for this, as it can create retain cycles. Instead, SwiftUI Introspect offers a `@Weak` property wrapper behind the Advanced SPI:
328
324
329
325
```swift
330
326
importSwiftUI
@@ -347,18 +343,18 @@ struct ContentView: View {
347
343
Note for library maintainers
348
344
----------------------------
349
345
350
-
If your library depends on SwiftUI Introspect, declare your dependency with a version range that spans at least the **last two major versions**rather than bumping straight to the latest one. This avoids conflicts when apps depend on SwiftUI Introspect directly or through multiple libraries at once. For example:
346
+
If your library depends on SwiftUI Introspect, declare a version range that spans at least the **last two major versions**instead of jumping straight to the latest. This avoids conflicts when apps pull the library directly or through multiple dependencies. For example:
Supporting a wider range is safe because SwiftUI Introspect is essentially a “finished” library: no new features will be added, only support for newer platform versions. Thanks to [`@_spi(Advanced)` imports](https://github.com/siteline/swiftui-introspect#introspect-on-future-platform-versions), it’s already future-proofed without requiring frequent version bumps.
352
+
A wider range is safe because SwiftUI Introspect is essentially “finished”: no new features will be added, only newer platform versions and view types. Thanks to [`@_spi(Advanced)` imports](https://github.com/siteline/swiftui-introspect#introspect-on-future-platform-versions), it is already future proof without frequent version bumps.
357
353
358
354
Community projects
359
355
------------------
360
356
361
-
Here's a list of open source libraries powered by the SwiftUI Introspect library:
357
+
Here are some open source libraries powered by SwiftUI Introspect:
0 commit comments