How to set up iOS session replay
Sep 11, 2024
Session replay is a useful support tool for understanding how users are interacting with your iOS app. It also helps you debug and recreate issues.
To show how you to set it up with PostHog, in this tutorial we create a basic UIKit app, add PostHog, and enable session recordings.
Note: While this tutorial uses UIKit, similar concepts apply to SwiftUI. See our docs for more details.
1. Create a basic iOS app
Our sample app will have two screens:
- The first screen is a
login
screen with name, email, and password text fields. - The second screen is a simple screen with welcome text and logout button.
The first step is to create a new app. Open Xcode and click Create new project. Select iOS as your platform, then App and press Next. Give your app a name, select Storyboard
as the interface, and the defaults for everything else. Click next and then Create.
Then, replace your code in ViewController
with the following:
import UIKitclass ViewController: UIViewController {private let nameTextField = UITextField()private let emailTextField = UITextField()private let passwordTextField = UITextField()private let loginButton = UIButton(type: .system)override func viewDidLoad() {super.viewDidLoad()setupUI()}private func setupUI() {view.backgroundColor = .whitetitle = "Login"// Name TextFieldnameTextField.placeholder = "Name"view.addSubview(nameTextField)// Email TextFieldemailTextField.placeholder = "Email"emailTextField.keyboardType = .emailAddressview.addSubview(emailTextField)// Password TextFieldpasswordTextField.placeholder = "Password"passwordTextField.isSecureTextEntry = trueview.addSubview(passwordTextField)// Login ButtonloginButton.setTitle("Login", for: .normal)loginButton.backgroundColor = .systemBlueloginButton.setTitleColor(.white, for: .normal)loginButton.layer.cornerRadius = 10loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)view.addSubview(loginButton)setupConstraints()}private func setupConstraints() {nameTextField.translatesAutoresizingMaskIntoConstraints = falseemailTextField.translatesAutoresizingMaskIntoConstraints = falsepasswordTextField.translatesAutoresizingMaskIntoConstraints = falseloginButton.translatesAutoresizingMaskIntoConstraints = falseNSLayoutConstraint.activate([nameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),nameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),nameTextField.heightAnchor.constraint(equalToConstant: 44),emailTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20),emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),emailTextField.heightAnchor.constraint(equalToConstant: 44),passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 20),passwordTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),passwordTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),passwordTextField.heightAnchor.constraint(equalToConstant: 44),loginButton.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 40),loginButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),loginButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),loginButton.heightAnchor.constraint(equalToConstant: 44)])}@objc private func loginButtonTapped() {if !emailTextField.text!.isEmpty && !passwordTextField.text!.isEmpty {let welcomeVC = WelcomeViewController()welcomeVC.modalPresentationStyle = .fullScreenpresent(welcomeVC, animated: true, completion: nil)}}}
Next, create a new file WelcomeViewController.swift
with the following:
import UIKitclass WelcomeViewController: UIViewController {private let welcomeLabel = UILabel()private let logoutButton = UIButton(type: .system)override func viewDidLoad() {super.viewDidLoad()setupUI()}private func setupUI() {view.backgroundColor = .whitetitle = "Welcome"// Welcome LabelwelcomeLabel.text = "Welcome!"welcomeLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)welcomeLabel.textAlignment = .centerview.addSubview(welcomeLabel)// Logout ButtonlogoutButton.setTitle("Logout", for: .normal)logoutButton.backgroundColor = .systemRedlogoutButton.setTitleColor(.white, for: .normal)logoutButton.layer.cornerRadius = 10logoutButton.addTarget(self, action: #selector(logoutButtonTapped), for: .touchUpInside)view.addSubview(logoutButton)setupConstraints()}private func setupConstraints() {welcomeLabel.translatesAutoresizingMaskIntoConstraints = falselogoutButton.translatesAutoresizingMaskIntoConstraints = falseNSLayoutConstraint.activate([welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),welcomeLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),logoutButton.topAnchor.constraint(equalTo: welcomeLabel.bottomAnchor, constant: 40),logoutButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),logoutButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),logoutButton.heightAnchor.constraint(equalToConstant: 44)])}@objc private func logoutButtonTapped() {presentingViewController?.dismiss(animated: true, completion: nil)}}
Our basic setup is now complete. Build and run your app to see it in action.
2. Add PostHog to your app
With our app set up, it’s time to install and set up PostHog. If you don't have a PostHog instance, you can sign up for free.
First, add posthog-ios
as a dependency to your app using Swift Package Manager (or if you prefer, you can use CocoaPods).
To add the package dependency to your Xcode project, select File > Add Package Dependency
and enter the URL https://github.com/PostHog/posthog-ios.git
. Select posthog-ios
and click Add Package.
Note: Session replay requires SDK version 3.8.3
or higher.
Next, configure your PostHog instance in didFinishLaunchingWithOptions
inside AppDelegate
. You can find your project API key and instance address in your PostHog project settings:
import UIKitimport PostHog@mainclass AppDelegate: UIResponder, UIApplicationDelegate {func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {let POSTHOG_API_KEY = "<ph_project_api_key>"let POSTHOG_HOST = "https://us.i.posthog.com" // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'let configuration = PostHogConfig(apiKey: POSTHOG_API_KEY, host: POSTHOG_HOST)configuration.sessionReplay = trueconfiguration.sessionReplayConfig.maskAllTextInputs = falseconfiguration.sessionReplayConfig.screenshotMode = truePostHogSDK.shared.setup(configuration)return true}// rest of your existing code
To check your setup, build and run your app a few times. Enter in any values in the text fields and click the Log in button. You should start see recordings in the session replay tab in PostHog 🎉.
3. (Optional) Mask sensitive data
Your replays may contain sensitive information. For example, if you're building a banking app you may not want to capture how much money a user has in their account. PostHog tries to automatically mask sensitive data (like the password text field), but sometimes you need to do it manually.
To replace any type of UIView
with a redacted version in the replay, set the accessibilityIdentifier
or accessibilityLabel
to ph-no-capture
.
In the below code, we do this for the welcome text in WelcomeViewController
:
// your existing code// Welcome LabelwelcomeLabel.text = "Welcome!"welcomeLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)welcomeLabel.textAlignment = .centerwelcomeLabel.accessibilityIdentifier = "ph-no-capture"view.addSubview(welcomeLabel)// rest of your existing code
Now, the welcome messages shows up like this in replays:
Note: Masking currently doesn't work with SwiftUI. There's an open issue to fix this.
Further reading
Subscribe to our newsletter
Product for Engineers
Join 25k+ subscribers learning how to build successful products and become better engineers.
We'll share your email with Substack