Integrating Fullstory into a SwiftUI App

With Fullstory Plugin version 1.31.0, SwiftUI support in Fullstory is available to all mobile customers.  Setting up SwiftUI support involves a few additional steps compared to setting up UIKit support, and SwiftUI functionality is somewhat different than UIKit support.  For more information on the differences between Fullstory for UIKit and Fullstory for SwiftUI, please see the companion article that describes these in more detail

To start off with, follow the setup process described in our Getting Started with iOS Capture article, then continue to follow the steps in this article.  The general flow of steps that you will need to take are as follows:

  • Enable SwiftUI support in your Info.plist. You need to explicitly enable SwiftUI support in your application because there are some key differences between Fullstory for SwiftUI and UIKit that you should be aware of.
  • Choose a selector version in your Info.plist.  Doing so ensures that future versions of the Fullstory plugin do not change the behavior of your privacy rules.
  • Use .fsMask() and .fsUnmask(), as appropriate, on SwiftUI View objects to implement your privacy rules.  Although privacy rules work on classes, it may be preferable to directly apply masking rules to View objects in this version of the Fullstory plugin to ensure that changes in SwiftUI keep your application private.
  • Use .fsAddClass() on SwiftUI View objects to provide semantic information on views that you might want to search for in the future.  Fullstory for SwiftUI requires that you tag views directly in code that you might later interact with in the Fullstory web interface.

Let’s discuss each of these steps in more detail.

Note: Transitioning from the SwiftUI Early Access program

For customers that previously were part of Fullstory SwiftUI Early Access program, a few things have changed.  

  • There is no longer a separate SwiftUI build, and the SwiftUI APIs are available in our general-availability SDK. To update, follow the setup process linked above with version 1.31.0 or greater.
  • In order to use the SwiftUI APIs, you will need to modify your Info.plist as described below, or you will receive errors at compile- and run-time.  
  • Additionally, since SwiftUI is now part of our general-availability builds, you may now use Swift Package Manager to add SwiftUI to your application.

Enabling SwiftUI support in Info.plist

Because SwiftUI APIs behave differently from Fullstory for UIKit – particularly, in ways that could have privacy implications – you need to explicitly enable SwiftUI support for your application.  To do this, modify your Info.plist (after reading the rest of this article, of course!) by adding a boolean SwiftUIEnabled key to the FullStory dictionary; the value of SwiftUIEnabled should be YES. When added correctly, it should look as follows:

CleanShot_2022-09-29_at_12.37.35_2x.png

Choosing a selector version

There are some limitations to the selectors you can create in a SwiftUI View (see this section in our SwiftUI companion article) . In future versions of our plugin, we expect to add more information to the selectors that resolve those limitations. However, to ensure that our future plugin releases don’t change your selectors without you opting in to those changes, you need to set your application’s Selector Version in your Info.plist.  

To do this, create a numerical key titled SwiftUISelectorVersion in the FullStory dictionary in your Info.plist file, as shown below.  Set this key to the selector version that you want your application’s privacy rules to conform to.  (In this release of the plugin, the only selector version supported is ‘1’.)

CleanShot_2022-09-29_at_12.37.44_2x.png

Committing to a selector version forces the Fullstory plugin to generate compatible selectors, even if new features are available in the future.  When you commit to a selector version, this also lets Fullstory know that it can use selectors when indexing tap events to search for your Fullstory searches, and lets you see selectors in the Page Insights view of the Fullstory web UI. 

Masking, Unmasking, and Annotating SwiftUI Views

Because Fullstory’s SwiftUI API does not do automatic detection of the SwiftUI View hierarchy, it’s important to directly annotate any attribute that you may want to query in Fullstory – and even more important to annotate any View that you may later want to apply a privacy rule to.  Once your SwiftUI application is in the field, if a View does not have a Fullstory class associated with it, you will not be able to mask or exclude it from the Fullstory web UI! 

SwiftUI Views can be directly annotated with the .fsAddClass(), .fsMask(), .fsUnmask(), and .fsExclude() APIs, which act as SwiftUI View Modifiers.  Only annotations that are explicitly applied through these Modifiers will be visible in playback; the Fullstory plugin cannot detect other behavior of Views, such as text labels or whether input elements are enabled or disabled, so you may wish to annotate those as Fullstory view classes as needed. 

SwiftUI view hierarchy support in the initial release of the SwiftUI plugin is rudimentary, and it’s best to avoid relying on hierarchical selector rules for privacy.  (For more information on this, see the companion article, What’s Different in Fullstory with SwiftUI?)  Instead, the best practice in this version is to directly use .fsMask(), .fsUnmask(), and .fsExclude() APIs on Views when you need to change their privacy settings– and to reserve .fsAddClass() for elements that you might want to inspect using Fullstory’s analytics tools later.

A sample of Fullstory SwiftUI APIs

The following code example shows the usage of Fullstory SwiftUI APIs in a SwiftUI program.

import SwiftUI

import FullStory

@available(iOS 13, *)
struct StackedMasked: ViewListEntry, View {
   var body: some View {
       let paddingY: CGFloat = 20
       let paddingX: CGFloat = 40
        let padding = EdgeInsets(top: paddingY, leading: paddingX, bottom: paddingY, trailing: paddingX)

       ScrollView(.vertical) {
           VStack(alignment: .center, spacing: paddingY) {
               Text("Default masked text")
               Text("This is unmasked via the helper")
                   .fsUnmask() // This will unmask the Text view, and also adds the .fs-unmask class
               Text("Helper masked text")
                   .fsMask()// This adds the .fs-mask class. Since Mobile is Private by Default, this is redundant
               Text("Helper masked text")
                   .fsExclude()// This adds the .fs-exclude class, and this won’t get captured
               Text("Class masked text")
                   .fsAddClass(.mask) // You can also manually add a class and use the .mask, .exclude, and .unmask shorthand
               if #available(iOS 14, *) {
                   Label("Helper masked text with an image", systemImage: "bolt.fill")
                       .fsMask()
               }
               Text("Unmasked via the class! (Also has an extra class \"checkout_btn\")")
                  .gesture(
                       TapGesture()
                           .onEnded { _ in
                               // Because this View is annotated with a class, 
                              //you will be able to search for users who tapped 
                              //on this View using the CSS Selector .checkout_btn

                            //And as always, you can also use custom events if
                               // you need to
                            FS.event(“product_purchased”, [“product_sku”: 12345])
                           }
                   )
                   .fsAddClasses(["checkout_btn", .unmask]) // fsAddClasses lets you add multiple classes
           }
           .padding(padding)
       }
   }

}

Need to get in touch with us?

The Fullstory Team awaits your every question.

Ask the Community Technical Support