# Native iOS

[](https://swiftpackageindex.com/pendo-io/pendo-mobile-sdk)
[](https://swiftpackageindex.com/pendo-io/pendo-mobile-sdk)\




>[!NOTE]
>The following integration instructions are relevant for SDK 3.0 or higher.
Follow our migration instructions to [upgrade from SDK 2.x to 3.0](/migration-docs/README.md) or refer to our [2.x integration instruction](https://github.com/pendo-io/pendo-mobile-sdk/blob/2.22.5/README.md).
>[!IMPORTANT]
>SwiftUI codeless solution is fully supported from `iOS 15`.
SwiftUI screen navigation tracking is available from `iOS 13`.
>[!IMPORTANT]
>Requirements:
>- Deployment target of `iOS 11` or higher
>- Swift Compatibility `5.7` or higher
>- Xcode `14` or higher
## Step 1. Add the Pendo SDK
### Cocoapods:
1. Open the _Podfile_.
2. Add: `pod 'Pendo'`.
### Swift Package Manager:
1. Open _File -> Add Packages_.
2. Search for: `https://github.com/pendo-io/pendo-mobile-sdk`.
3. Select _Up to Next Major Version_.
## Step 2. Establish a connection to Pendo's server on app launch
>[!NOTE]
>Find your API key in the Pendo UI under `Settings` > `Subscription settings` > select an app > `App Details`.
Identify if your app project contains an `AppDelegate` file or a `SceneDelegate` file. Pure SwiftUI projects do not include either of these files. To use Pendo in your app, you will need to create one of them.
1. If using the `AppDelegate` file, implement the following:
Swift Instructions - Click to expand or collapse
```swift
import UIKit
import Pendo
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
PendoManager.shared().setup("YOUR_API_KEY_HERE")
// your code here ...
return true
}
}
```
Objective-C Instructions - Click to expand or collapse
```objectivec
#import "AppDelegate.h"
@import Pendo;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[PendoManager sharedManager] setup:@"YOUR_API_KEY_HERE"];
// your code here ...
return YES;
}
@end
```
2. If using the `SceneDelegate` file, implement the following:
Swift Instructions - Click to expand or collapse
```swift
import Pendo
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
PendoManager.shared().setup("YOUR_API_KEY_HERE")
// your code here ...
}
```
Objective-C Instructions - Click to expand or collapse
```objectivec
@import Pendo;
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
[[PendoManager sharedManager] setup:@"YOUR_API_KEY_HERE"];
// your code here ...
}
```
## Step 3. Start a new session to track a visitor and to display guides
To begin tracking a visitor's analytics and to display guides, call the `startSession` API. The call to the `startSession` API can be conducted immediately after calling the `setup` API or anywhere else in the code, such as completing the login process of your app.
Swift Instructions - Click to expand or collapse
```swift
var visitorId = "John Doe"
var accountId = "ACME"
var visitorData: [String : any Hashable] = ["age": 27, "country": "USA"]
var accountData: [String : any Hashable] = ["Tier": 1, "size": "Enterprise"]
PendoManager.shared().startSession(visitorId, accountId:accountId, visitorData:visitorData, accountData:accountData)
```
Objective-C Instructions - Click to expand or collapse
```objectivec
[[PendoManager sharedManager] startSession:@"someVisitor" accountId:@"someAccount" visitorData:@{@"age": @27, @"country": @"USA"} accountData:@{@"Tier": @1, @"size": @"Enterprise"}];
```
>[!TIP]
>To begin a session for an anonymous visitor, pass ```nil``` or an empty string ```""``` as the Visitor ID. You can call the `startSession` API more than once and transition from an anonymous session to an identified session (or even switch between multiple identified sessions).
### Supporting SwiftUI with older SDK (below 3.1) additional step
If using SDK below 3.1 the `pendoEnableSwiftUI()` modifier must be applied to the application rootView. If there are multiple rootViews (ex. usage of multiple UIHostingControllers), apply the modifier to each main rootView. See example below:
```swift
struct YourView: View {
var body: some View {
Text("RootView")
.pendoEnableSwiftUI()
}
}
```
## Step 4. Configure Pairing Mode for tagging and testing
>[!NOTE]
>Find your scheme ID in the Pendo UI under `Settings` > `Subscription settings` > select an app > `App Details`.
For additional information, see page tagging
and guide testing.
### Add the Pendo URL scheme to the **info.plist** file
Navigate to your **App Target > Info > URL Types** and create a new URL by clicking the plus (+) button.
Set the **Identifier** to `pendo-pairing` or an identifiable name of your choosing.
Set **URL Scheme** to `YOUR_SCHEME_ID_HERE`.
### Configure the app to connect to Pairing Mode
1. If using `AppDelegate`, add or modify the `openURL` function:
Swift Instructions - Click to expand or collapse
```swift
import Pendo
...
func application(_ app: UIApplication,open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if url.scheme?.range(of: "pendo") != nil {
PendoManager.shared().initWith(url)
return true
}
// your code here...
return true
}
```
Objective-C Instructions - Click to expand or collapse
```objectivec
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
if ([[url scheme] containsString:@"pendo"]) {
[[PendoManager sharedManager] initWithUrl:url];
return YES;
}
//your code here...
return YES;
}
```
2. If using `SceneDelegate`, add or modify the `openURLContexts` function:
Swift Instructions - Click to expand or collapse
```swift
import Pendo
...
func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
if let url = URLContexts.first?.url, url.scheme?.range(of: "pendo") != nil {
PendoManager.shared().initWith(url)
}
}
```
Objective-C Instructions - Click to expand or collapse
```objectivec
- (void)scene:(UIScene *)scene openURLContexts:(nonnull NSSet *)URLContexts {
NSURL *url = [[URLContexts allObjects] firstObject].URL;
if ([[url scheme] containsString:@"pendo"]) {
[[PendoManager sharedManager] initWithUrl:url];
}
// your code here ...
}
```
### An additional required Step to configure Pairing Mode for SwiftUI
If the entry point to your app is a `struct` attributed with `@main`, your SwiftUI application will not respond to the method `application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool`.
To handle URL schemes in your SwiftUI app, add the `.onOpenURL()` modifier to your main view.
```swift
import Pendo
@main
struct YourApp: App {
let persistenceController = PersistenceController.shared
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
return WindowGroup {
TabView {
YourView().tabItem {
Image("Icon")
Text("Text")
}
}
.pendoEnableSwiftUI()
.onOpenURL(perform: handleURL)
}
}
func handleURL(_ url: URL) {
_ = appDelegate.application(UIApplication.shared, open: url, options: [:])
}
}
```
## Step 5. Verify installation
1. Test using Xcode:
Run the app while attached to Xcode.
Review the Xcode console and look for the following message:
`Pendo Mobile SDK was successfully integrated and connected to the server.`
2. In the Pendo UI, go to Settings > Subscription Settings.
3. Select your application from the list.
4. Select the **Install Settings** tab and follow the instructions under Verify Your Installation to ensure you have successfully integrated the Pendo SDK.
5. Confirm that you can see your app as `Integrated` under subscription settings.
## SwiftUI limitations
SwiftUI tracking of Page changes is based on the application events emitted by the following navigation components: `NavigationView`,`NavigationStack`,`NavigationSplitView`,`TabView`,`NavigationLink`,`ActionSheet`,`Sheets`,`.alert`,`.confirmationDialog` and `PopOvers`, under the hood SwiftUI still uses UIKit navigation and as such Pendo will track those changes automatically by identifying those Pages with unique identifier we extract from the declarative definition of the Page or from the underline structure of your app. Rendering new views on the Page will not be tracked by our SDK automatically.
**Specific Limitations**
1. **Page Changes**:
a. If your application renders new views conditionally or dynamically (e.g `ZStack` views that you treat as distinct Pages) *without* using standard navigation containers mentioned above, Pendo might not automatically recognize this as a distinct Page change. To ensure these views are tracked as separate Pages in Pendo analytics, you can manually designate them using the [`.trackPage(pageId: "page_id")`](/api-documentation/native-ios-apis.md#viewtrackpage) modifier on the relevant view. Ensure the `pageId` provided is unique across your application.
b. If the tagged Page identifier such as `retroactiveScreenId` or `swiftUIIdentifier` are not unique enough you can enhance it by selecting unique Page Identifier in Pendo Web designer OR apply your own Page id via [`.trackPage(pageId: "page_id")`](/api-documentation/native-ios-apis.md#viewtrackpage)
2. **Tagging**:
Pendo's Feature tagging relies heavily on iOS accessibility services to gather information like accessibilityHint, accessibilityIdentifier, accessibilityLabel, and user interactions. While iOS typically provides these accessibility elements by default, there might be instances where UI elements are not automatically tagged as expected by the Pendo SDK. In such cases, you can use the pendoRecognizeClickAnalytics() modifier. This API helps by creating an accessibility element, combining its children, and marking it as userInteractionEnabled. This allows Pendo to correctly identify the element as taggable and record click analytics for it.
3. **List Elements**:
SwiftUI's `List` and `ForEach` are used to create dynamic lists. Under the hood, SwiftUI's `List` uses a `UICollectionView`, allowing the Pendo SDK to tag individual list elements.
Click analytics are recorded only for elements that are clickable.
- **Automatic tracking**: When list rows are wrapped in a `Button` or `NavigationLink`, Pendo tracks clicks automatically. Clicks are also tracked when using the `.onChange` modifier with a `List` to handle selection changes. In these cases, we detect clicks via `collectionView:didSelectItemAtIndexPath:`.
- **Manual intervention**: If you use gestures like `.onTapGesture`, the Pendo SDK might not record clicks. In such cases, you need to help Pendo recognize the interaction. You can use the `pendoRecognizeClickAnalytics()` API, or Apple's native accessibility modifiers:
```swift
.accessibilityElement(children: .combine)
.accessibilityAddTraits([.isButton])
```
This ensures that Pendo can record click analytics for the element.
4. **Container Views**:
Container views with `TapGestures` modifiers don't always generate underlying accessibility elements and may cause Pendo SDK to fail tagging them as clickable elements and collecting analytics. This is because such views are purely declarative and serve as instructions for their child elements.
Examples of these views include `VStack`, `HStack`, `ZStack`, `LazyHStack`, `LazyVStack`, `LazyVGrid`, `GeometryReader`, and `LazyHGrid`. In that case we recommend using the `pendoRecognizeClickAnalytics()` API on the specific element to ensure interactions are properly recorded.
5. **UIContextMenu,Menu,.contextMenu**:
The UIContextMenu control is not supported in both Swift and SwiftUI. As a result, any interactions with context menus created using this control will not be tracked by the SDK.
## Developer documentation
- API documentation available [here](/api-documentation/native-ios-apis.md).
- See [Native application with Flutter components](/other/native-with-flutter-components.md) integration instructions.
- Sample apps with examples of Feature tagging and how Pendo analytics work.
(pay attention to comments with _PENDO CHANGE_. In some cases these require minor changes of integration code or adding a background color)
- [ACHNBrowserUI](https://github.com/pendo-io/ACHNBrowserUI)
- [TeslaApp](https://github.com/pendo-io/Tesla_Clone_Swiftui)
## SwiftUI Troubleshooting
_Why aren't some elements being tagged correctly in SwiftUI?_
* **Missing Accessibility Traits**: Container views with an `.onTapGesture` modifier might not be automatically tagged. To ensure they are, you can add accessibility traits to help our SDK identify the view as an interactive element. For example:
```swift
.accessibilityElement(children: .combine)
.accessibilityAddTraits([.isButton])
```
* **Tagging elements inside Overlays**: The Pendo SDK automatically detects and tags elements within most SwiftUI `Overlays`. However, if you find that elements inside an overlay are not taggable, it may be because the overlay is not part of the top-most view controller's hierarchy. In these rare cases, you can enable the `pendoOptions.scanFromRootViewController` flag. This allows the SDK to scan from the root view controller, making overlay content accessible. Be aware that this performs a deeper scan of the view hierarchy and may impact performance, so use it only when necessary.
**Using Our API** :
- `pendoRecognizeClickAnalytics()` - A Pendo-specific modifier to help recognize clickable views that are not automatically tagged. It applies Apple's native accessibility APIs under the hood to combine child elements and mark them as buttons. If you prefer not to use a Pendo API, or for better code clarity, you can apply these native modifiers yourself:
```swift
.accessibilityElement(children: .combine)
.accessibilityAddTraits([.isButton])
```
- `trackPage(pageId: "page_id")` - If the Pendo SDK fails to uniquely identify your Page, use this API to manually designate the view as a Page with a unique Page name.
_Why do some of my SwiftUI screens have generic or irrelevant keywords in their screenId, and how can this be improved?_
* While we continue to refine screen identification for SwiftUI, make sure that your SwiftUI views are properly structured and identifiable. For now, this may require some manual adjustments to ensure each screen is tracked correctly.
_I have noticed performance issues in my app after integrating Pendo SDK. What should I do?_
* **Disable Some Information Collection** - To improve performance, particularly on iPads, consider disabling some of the things Pendo collects while scanning your Page:
* **Hashed Texts Collection** - Set enableTextCollectionSwiftUI to false in pendoOptions:
```
let options = PendoOptions()
options.configs = ["enableTextCollectionSwiftUI": false]
PendoManager.shared().setup(prodAppKey, with: options)
```
_Please note that in that case feature analytics will be based only on accessibility data._
* **Optimizing Scanning Depth** - If performance issues persist, adjust the scan depth settings. Consult our support team to configure this setting for optimal performance.
## General Troubleshooting
- For technical issues, please [review open issues](https://github.com/pendo-io/pendo-mobile-sdk/issues) or [submit a new issue](https://github.com/pendo-io/pendo-mobile-sdk/issues).
- See our [release notes](https://developers.pendo.io/category/mobile-sdk/).
- For additional documentation, visit our [Help Center](https://support.pendo.io/hc/en-us/categories/23324531103771-Mobile-implementation).