This guide covers selector version 4 (automatic, compile-time Preview). For which version to choose, see Choosing a selector version.
Introduction
Fullstory can automatically generate selectors for SwiftUI Views. Version 4 of SwiftUI selectors is compile-time: instead of deriving a selector by inspecting views as they render, the FullStorySwiftUIWrapper tool rewrites your SwiftUI source before it is compiled, substituting Fullstory proxy types for the SwiftUI types they stand in for. Each proxy renders identically to the original and carries the information needed to describe the view, so the selector is determined by the structure of your code rather than reconstructed at runtime.
This produces more accurate and more stable selectors than version 3, and it supports a much larger set of SwiftUI view types and modifiers. The tool also performs cross-file analysis across your whole project and its Swift Package dependencies, so it can tell when a name like List refers to one of your own types rather than SwiftUI's, and leave it alone.
This feature is in Preview and subject to change in future versions of the Fullstory SDK
The transformation is applied to your own source files, in place, as an Xcode pre-build action. Every edit is bracketed by /*Fullstory_XFORM_start*/ and /*Fullstory_XFORM_end*/ markers or by a __FsS prefix, and the tool can reverse all of them exactly. Nothing is deleted or reflowed, so your line numbers — and therefore crash reports and debugger breakpoints — stay aligned with your original source.
Before you begin
- Make sure the Fullstory SDK is configured — follow Getting Started with iOS Data Capture. This includes the
FullStoryCommandLinebuild phase. - In your Target's Build Settings, set User Script Sandboxing to
NO. - If you previously used version 3 selectors, remove any Build Rule or Pre-action that runs
FullStorySwiftUITransformer. Running both tools in the same build is not supported.
Installing the package
The selectors library and the FullStorySwiftUIWrapper tool ship together in a public Swift package.
-
In Xcode, choose File > Add Package Dependencies… and add:
https://github.com/fullstorydev/fullstory-swiftui-selectors-ios
Pin it to an Exact Version. Each
FSSwiftUISelectorsrelease is built against one specific Fullstory build, so its manifest pins the matching Fullstory version exactly. - Add the FSSwiftUISelectors library to your app target (under General > Frameworks, Libraries, and Embedded Content), the same way the Fullstory framework is added.
-
Because you will also be calling Fullstory APIs directly (
FS.setup(…)and friends), most projects should declare both packages explicitly, at the same version. In aPackage.swiftthat looks like:dependencies: [ .package(url: "https://github.com/fullstorydev/fullstory-swiftui-selectors-ios", exact: "1.74.0"), .package(url: "https://github.com/fullstorydev/fullstory-swift-package-ios", exact: "1.74.0"), ]
The two pins must agree. Because the selectors package pins Fullstory with exact:, a mismatch surfaces as a package resolution error rather than a runtime failure.
Configuration
Update your Info.plist (or the Info tab of your project properties) to include the following in the FullStory dictionary:
-
SwiftUIEnabledtypeBooleantoYES -
SwiftUISelectorVersiontypeNumberto4 -
SwiftUISelectorPreviewtypeNumberto1
Running the transformer
Run the included FullStorySwiftUIWrapper using either:
-
(Recommended) Pre-actions, which modifies the source in your repository, supports the use of debug breakpoints, and enables cross-file analysis.
OR - Build Rules, which don't modify the source in your repository, but transform one file at a time with no cross-file analysis.
Swift Packages can only be transformed using Pre-actions.
Using Pre-actions (recommended)
With this method the tool pre-transforms the source in your project, which means all transformations remain in your source repository. They live within /*Fullstory_XFORM_start*/ and /*Fullstory_XFORM_end*/ for easy identification and removal, if needed.
- From the menu bar, select Product > Scheme > Edit Scheme, expand Build and select Pre-actions.
- Click the + button to add a New Run Script Action, and make sure your Target is selected for Provide build settings from.
-
Paste the script below, including all quotes, as a single line. It runs
FullStorySwiftUIWrapperfrom the package'stoolsfolder over both your project's source and the resolved Swift Package sources:"${BUILD_DIR%Build/*}SourcePackages/artifacts/fullstory-swiftui-selectors-ios/FSSwiftUISelectors/tools/FullStorySwiftUIWrapper" "$SRCROOT" "${BUILD_DIR%/Build/*}" --verboseThe first path is your project's source root; the second is the DerivedData directory that holds the resolved package checkouts. Pass as many directories as you like — each is walked recursively, and a file reachable from more than one of them is transformed only once.
-
If your SwiftUI code lives in local Swift Packages of your own, add
--transform-package-swiftso theirPackage.swiftmanifests gain theFSSwiftUISelectorsdependency they now need:… "$SRCROOT" "${BUILD_DIR%/Build/*}" --verbose --transform-package-swift -
To leave a third-party package alone, exclude it by name. See Command line options for the full list:
… --exclude-remote-package swift-composable-architecture --exclude-remote-package Nuke
Resolved Swift Package dependencies are transformed by default — SwiftUI code in a package you depend on still renders in your app. Build intermediates, version-control metadata, and package build products are skipped automatically.
When transforming Swift Packages, it may help to Reset Package Caches before building. Otherwise, your app may build with cached copies instead of the transformed package sources.
Make sure that you aren't also running FullStorySwiftUITransformer (the version 3 tool) as part of your build.
Using Build Rules
Repeat this step for every Xcode Target that contains custom SwiftUI Views (structs that implement the View protocol). This step does not instrument Swift Packages — please use Pre-actions, above, to instrument packages.
Build Rules invoke the tool on one file at a time, so it runs in single-file mode and cannot perform cross-file analysis. In this mode every protocol is treated as if it might have a conformer the tool cannot see, and shadowed type names cannot be detected. The tool is deliberately conservative when it cannot tell, so some views will be left uninstrumented that Pre-actions would have instrumented. Pre-actions are strongly preferred for version 4.
- In the project navigator, select your App's Project, then select your Target in the editor panel.
- Open the Build Rules tab, click the + button (next to "All") to add a rule to the top.
-
Choose to process Swift source files using Custom script and paste the script below, including all quotes, as a single line:
"${BUILD_DIR%Build/*}SourcePackages/artifacts/fullstory-swiftui-selectors-ios/FSSwiftUISelectors/tools/FullStorySwiftUIWrapper" "$SCRIPT_INPUT_FILE" "$DERIVED_FILE_DIR/${INPUT_FILE_BASE}_transformed.swift" - Under the "Output Files" section of the rule, click + and edit the rule to say $(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)_transformed.swift
-
- Click the + button again to add another rule above the one you just added.
- Choose Process > Source files with names matching: enter *_transformed.swift.
- Choose Using > Swift Compiler.
- The ordering of the two rules is important — the
*_transformed.swiftrule must sit above the custom script rule. -
Because this method generates new source files that are not checked into your repository, debugging breakpoints added to the original source will not be hit. The
_transformedsuffix also changes the way files are reported by any tool that uses compiled file names, for example crash reports. Line numbers, type names, and everything other than the instrumentation itself are preserved.You may use a different suffix than _transformed if you wish, as long as it uniquely identifies transformed SwiftUI files.
Command line options
FullStorySwiftUIWrapper takes one or more input paths, followed by any of the options below.
Input paths
| Form | Behavior |
|---|---|
<directory> […] |
Directory mode. Every Swift file underneath each directory is transformed in place, in parallel, including resolved SPM dependencies. This mode performs tree-wide analysis and can rewrite Package.swift manifests. This is the recommended form. |
<input.swift> [output.swift] |
File mode. Transforms a single file. If no output path is given, the file is transformed in place. No cross-file analysis is performed, so the transformation is more conservative. |
Directory and file inputs cannot be mixed in one invocation.
Options
| Option | Effect |
|---|---|
--verbose |
Print additional information, including the number of files considered and the name of each file that was transformed. |
--warn-on-error |
Emit warnings instead of errors. A file that fails to transform is skipped — left uninstrumented but otherwise untouched — and the build continues. Without this flag, a failure fails the build. |
--verify-transform |
Re-parse each file after transforming it, and fail that file if the result is unparseable Swift while the original was not. Off by default because it costs a second parse of every file. Pair it with --warn-on-error to leave such a file uninstrumented and let the build continue. |
--untransform |
Remove Fullstory SwiftUI transformations instead of applying them, restoring your source byte-for-byte. Accepts the same input paths as a normal run. |
--exclude-remote-package <name> |
Directory mode only. Skip a resolved remote SPM package. The name is its checkout directory name under SourcePackages/checkouts — that is, the git repository's last path component with any .git removed (for example Nuke or swift-composable-architecture), not the module you import. Repeat the option to exclude multiple packages. |
--exclude-local-package <name> |
Directory mode only. Skip a local SPM package directory. Matches a directory with this name that contains a Package.swift at its root, and prunes its entire subtree. Repeat to exclude multiple packages. |
--transform-package-swift |
Also transform Package.swift manifests, adding the FSSwiftUISelectors dependency to the package and to each target whose sources were transformed. Use this when your own SwiftUI code lives in local Swift Packages. Automatically enabled when any of the options below is provided. |
--local-package-directory <path> |
Resolve the injected FSSwiftUISelectors dependency to a local package directory instead of the published package. Implies --transform-package-swift. Cannot be combined with the three --selectors-package-* options below. |
--selectors-package-url <url> |
Repository to depend on for FSSwiftUISelectors, overriding the published default. Implies --transform-package-swift. |
--selectors-package-version <version> |
Lowest FSSwiftUISelectors version to accept in an injected dependency, overriding the published default. Implies --transform-package-swift. |
--selectors-package-version-limit <version> |
Highest FSSwiftUISelectors version to accept in an injected dependency, overriding the published default. Implies --transform-package-swift. |
--lite |
Force lite mode: emit scoped imports rather than importing the whole FSSwiftUISelectors module, and skip constructor and view-modifier instrumentation. Intended for files in packages that do not depend on FSSwiftUISelectors. Most projects should not need this. |
Excluding individual files
Two directives control the tool from inside a source file. Add either string anywhere in the file, with no spaces.
| Directive | Effect |
|---|---|
//Fullstory_XFORM_disable |
The file is left exactly as it is, in both directions. It is neither transformed nor untransformed. Use this for a file whose current contents are correct as written. |
//Fullstory_WRAP_disable |
The file is untransformed and left that way. If it was previously instrumented, the instrumentation is removed. Use this to opt a file out of instrumentation. |
Troubleshooting
"Unable to find module dependency: 'FSSwiftUISelectors'"
Xcode resolves packages and plans the build before it runs pre-build actions, so a dependency the tool injects into a Package.swift during a build is invisible to the targets being built in that same build. The plan is then cached, and re-resolving does not discard it, so later builds repeat the same failure.
The tool prints a warning when this happens, naming the manifests it changed. Choose Product > Clean Build Folder and build again. Your transformed sources are preserved. This affects only the first build after a manifest changes.
A file fails to transform
Add --warn-on-error to your pre-action so the file is skipped with a warning and the build continues; that file is simply left uninstrumented. Please report the file to support so the rule can be fixed.
Reverting the transformation
Run the tool again with --untransform and the same input paths. Every edit is reversible, and round-tripping restores your source byte for byte. Removing the pre-action alone does not revert the source, since the original files are modified.
Package sources appear uninstrumented
Use File > Packages > Reset Package Caches and build again; the build may have used cached copies rather than the transformed package sources.
Additional notes
Where the tool cannot be certain that a name refers to a SwiftUI type — because you declared a type of the same name, or because a protocol may have a conformer outside the code being analyzed — it leaves that code alone. Missing instrumentation is preferable to a rewrite that would not compile, so some views may be uninstrumented by design.
Re-running the tool is safe and idempotent. It reverts any previous transformation before applying the current one, so incremental builds produce identical bytes and an upgrade to a newer version of the tool never layers on top of an older result.
If you have any questions, run into any issues, or have requests to add support for a view, please contact support! We will be very happy to help.