Option 2: Add Mobile SDK Setup Code Manually

If you prefer the freedom of writing all your code from scratch, you can create a project without copying Mobile SDK template files into your workspace. You can—and should—consult the template code to pick up boilerplate implementations for certain features.

Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations.

Important

The procedure here creates a basic Mobile SDK Swift app that implements an AppDelegate - SceneDelegate flow.

When you insert Mobile SDK into an iOS project without importing template files, you’re responsible for reconfiguring the app lifecycle according to Mobile SDK guidelines. For example, here are some required tasks:
  • Configure SalesforceApp as the main class
  • Initialize Mobile SDK
  • Call Mobile SDK to prompt for customer login and handle user switching
For a few optional features, Mobile SDK provides specific customizations that you can drop into your code.
As benefits, you have some additional flexibility to suit your development needs:
  • Code in Swift (recommended)

    or

    Objective-C (to support older apps; not described in this procedure)

  • Use AppDelegate for app-level initialization, and SceneDelegate to handle all UI lifecycle events. This path is recommended; it supports multiple windows in iPadOS, clarifies multi-scene handling, and separates app-level and scene-level code.

    or

    Use AppDelegate alone to handle app initialization and UI lifecycle events. This path is an option only for single-window apps that don’t require multiple window support in iPadOS. If you choose this route, it’s up to you to find places in AppDelegate for all Mobile SDK code that’s added here in SceneDelegate. See the iOS Native Template for examples.

These instructions provide minimal steps for enabling Mobile SDK in a runnable Swift app. At the end of this procedure, you can find additional sections on setting up optional features such as push notifications and IDP apps.

Note

Add or Edit Info.plist

Mobile SDK uses the project’s main Info.plist file to set the default Salesforce login host. In Xcode 12.4, the app wizard creates the Info.plist file for you. In Xcode 13, the app wizard for the SwiftUI interface doesn’t create the file, but you can copy one from a Mobile SDK template app.

Xcode 12.4
  1. In the Project Navigator, control-click MyMobileSDKApp | MyMobileSDKApp | Info.plist and select Open As | Source Code.
  2. Add the following key-value pair to the top-level dictionary:
    1<key>SFDCOAuthLoginHost</key>
    2<string>login.salesforce.com</string>
Xcode 13
Copy the Info.plist file to your project from the iOS Native Swift template. When the file is in place, adjust your Project Settings as follows:
  1. In your Project settings, select Build Phases.
  2. Under Copy Bundle Resources, select Info.plist, if it’s present, and click Remove Items (-).
  3. In Build Settings, search for “Info.plist File”.
  4. Set Generate Info.plist File to No.
  5. Set Info.plist File to the file’s path in your project (for example, “MyMobileSDKApp/Info.plist”.)

    Build Settings configuration for Info.plist

Add bootconfig.plist

Mobile SDK looks for a bootconfig.plist file to help your app start with the correct connected app values. You can copy this file to your project from a Mobile SDK iOS template project, but be sure to reset the values to your app’s preferences—especially remoteAccessConsumerKey and oauthRedirectURI.
  1. In an external text editor, create an empty text file.
  2. Copy the following text into the new file:
    1<?xml version="1.0" encoding="UTF-8"?>
    2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
    3    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    4<plist version="1.0">
    5<dict>
    6    <key>remoteAccessConsumerKey</key>
    7    <string>3MVG9Iu66FKeHhINkB1l7xt7kR8czFcCTUhgoA8Ol2Ltf1eYHOU4SqQRSEitYFDUpqRWcoQ2.dBv_a1Dyu5xa</string>
    8    <key>oauthRedirectURI</key>
    9    <string>testsfdc:///mobilesdk/detect/oauth/done</string>
    10    <key>oauthScopes</key>
    11    <array>
    12        <string>web</string>
    13        <string>api</string>
    14    </array>
    15    <key>shouldAuthenticate</key>
    16    <true/>
    17</dict>
    18</plist>
  3. Save the file as bootconfig.plist in your MyMobileSDKApp project's local disk directory.
  4. In Xcode’s Project Explorer, control-click MyMobileSDKApp | MyMobileSDKApp and select Add Files to “MyMobileSDKApp”.
  5. Select bootconfig.plist.
  6. To do now (if your connected app is available) or before you release your app:
    1. Open bootconfig.plist in the Xcode editor.
    2. Replace the remoteAccessConsumerKey value with the Consumer Key from your org's connected app.
    3. Replace the oauthRedirectURI value with the Callback URL from your org’s connected app.

Add a main.swift File

Mobile SDK requires its own UIApplication-based object, SFApplication, to monitor user event notifications. This class must be instantiated when iOS first initializes the main application object. To use this class instead of the default UIApplication class, you add a custom main.swift file to your project. You can then call the UIApplicationMain constructor with custom arguments.

  1. In your project, control-click MyMobileSDKApp | MyMobileSDKApp and select New File.
  2. Select Swift File, name it “main.swift”, and then click Create. Xcode creates a file with one line:
    1import Foundation
  3. Import SalesforceSDKCore.
    1import Foundation
    2import SalesforceSDKCore
  4. Create an instance of UIApplicationMain, passing in
    • “SFApplication” for the principalClassName argument
    • “AppDelegate” for the delegateClassName argument
    1UIApplicationMain(
    2    CommandLine.argc,
    3    CommandLine.unsafeArgv,
    4    NSStringFromClass(SFApplication.self), //principalClassName
    5    NSStringFromClass(AppDelegate.self)    //delegateClassName
    6)

See UIApplicationMain(_:_:_:_:) in the Apple Developer documentation.

Customize or Create the AppDelegate Class

If you tell the Xcode 12.4 app wizard to create a UIKit App Delegate lifecycle, it creates AppDelegate and SceneDelegate classes. The Xcode 13 app wizard doesn’t offer a lifecycle choice and doesn’t create an AppDelegate class. However, you can create the basic minimal version from scratch using the following instructions.
  1. Xcode 12.4: Start with the AppDelegate class in your project, keeping the three standard lifecycle functions.

    Xcode 13:

    1. In your project's MyMobileSDKApp | MyMobileSDKApp folder, control-click the MyMobileSDKAppApp.swift file and click Delete.
    2. Click Move to Trash.
    3. Control-click MyMobileSDKApp | MyMobileSDKApp and select New File.
    4. Select Swift File, name it “AppDelegate”, and then click Create. Xcode creates a file with one line:
      1import Foundation
  2. Paste the following boilerplate implementation of the AppDelegate life cycle methods.
    1import Foundation
    2
    3@main
    4class AppDelegate : UIResponder, UIApplicationDelegate {
    5    var window: UIWindow?
    6
    7    // MARK: UISceneSession Lifecycle
    8    func application(_ application: UIApplication, configurationForConnecting 
    9        connectingSceneSession: UISceneSession, 
    10        options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    11        // Called when a new scene session is being created.
    12        // Use this method to select a configuration to create the new scene with.
    13        return UISceneConfiguration(name: "Default Configuration", 
    14            sessionRole: connectingSceneSession.role)
    15    }
    16    
    17    func application(_ application: UIApplication, didDiscardSceneSessions 
    18        sceneSessions: Set<UISceneSession>) {
    19        // Called when the user discards a scene session.
    20        // If any sessions were discarded while the application was not running, 
    21        // this will be called shortly after 
    22        // application:didFinishLaunchingWithOptions.
    23        // Use this method to release any resources that were specific to the 
    24        // discarded scenes, as they will not return.
    25    }
    26
    27    // MARK: - App delegate lifecycle
    28    func application(_ application: UIApplication, didFinishLaunchingWithOptions 
    29        launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    30        // If you wish to register for push notifications, uncomment the line below.  
    31        // Note that, if you want to receive push notifications from Salesforce, 
    32        // you will also need to implement the 
    33        // application(application, didRegisterForRemoteNotificationsWithDeviceToken) 
    34        // method (below).
    35
    36        // self.registerForRemotePushNotifications()
    37        return true
    38    }
    39}
  3. Remove the @main attribute above the AppDelegate class declaration. The main.swift file has assumed the role of main.
  4. Import all Mobile SDK modules.
    1import Foundation
    2import MobileSync
  5. At the top of the class, override the init() method to initialize Mobile SDK. For example, to initialize all Mobile SDK libraries, call initializeSDK() on MobileSyncSDKManager:
    1override init() {
    2    super.init()
    3    MobileSyncSDKManager.initializeSDK()
    4}

Add an InitialViewController Class

To access Salesforce data, your app’s users must authenticate with a Salesforce org. Salesforce sends a login screen to your app to collect the user’s credentials and then returns OAuth tokens to your app.

Your app must include a view controller whose sole purpose is to host the Salesforce login screen. When no customer is logged in, your app sets this view controller as the root view. Traditionally, Mobile SDK apps name this view controller class InitialViewController. It requires only a stub implementation.
  1. In your project, control-click MyMobileSDKApp | MyMobileSDKApp and select New File.
  2. Select Swift File, name it “InitialViewController”, and then click Create.
  3. Add the following body:
    1import Foundation
    2import UIKit
    3
    4class InitialViewController: UIViewController {
    5}

Customize or Create the SceneDelegate Class

Use the SceneDelegate class to handle Salesforce logins, user switching, and root view instantiation. Login is required when no user is logged in—when the app originally launches, and when the current user logs out. User changes occur between users who have logged in previously and remain authenticated. Mobile SDK AuthHelper functions simplify how these cases are handled:
  • AuthHelper.loginIfRequired(_:) decides for you whether login is needed
  • AuthHelper.registerBlock(forCurrentUserChangeNotifications:) listens for user change events.
These functions let you determine what happens after the login or change occurs. Typically, you respond by setting your app’s main view as the root view.

In the scene lifecycle, specific junctures are ideal for each of these AuthHelper calls. For example, register the block that handles user changes only one time, when the scene is first initialized. Also, call AuthHelper.loginIfRequired(_:_:) whenever the scene comes to the foreground. If the current user remains logged in, Mobile SDK merely executes your completion block.

At app startup, you set InitialViewController as the first root view controller. This view hosts the Salesforce login screen and, after a successful login, is replaced by your app’s root view controller. At runtime, if the current user logs out, you dismiss the current root view controller and replace it with InitialViewController. In both cases, after a new user logs in, you dismiss InitialViewController and set your app’s first view as the root view controller.

Xcode 13 only: In Xcode 13, the SwiftUI interface no longer uses SceneDelegate. However, iOS still supports it, and Mobile SDK currently uses it. Follow these steps to create it.
  1. In your project, control-click MyMobileSDKApp | MyMobileSDKApp and select New File.
  2. Select Swift File, name it “SceneDelegate”, and then click Create.
  3. Copy the following implementation into your new file.
    1import UIKit
    2import MobileSync
    3import SwiftUI
    4
    5class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    6
    7    var window: UIWindow?
    8
    9
    10    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 
    11        options connectionOptions: UIScene.ConnectionOptions) {
    12        // Use this method to optionally configure and attach the 
    13        // UIWindow `window` to the provided UIWindowScene `scene`.
    14        // If using a storyboard, the `window` property will automatically 
    15        // be initialized and attached to the scene. This delegate does not 
    16        // imply the connecting scene or session are new 
    17        // (see `application:configurationForConnectingSceneSession` instead).
    18
    19        // Create the SwiftUI view that provides the window contents.
    20        let contentView = ContentView()
    21
    22        // Use a UIHostingController as window root view controller.
    23        if let windowScene = scene as? UIWindowScene {
    24            let window = UIWindow(windowScene: windowScene)
    25            window.rootViewController = UIHostingController(rootView: contentView)
    26            self.window = window
    27            window.makeKeyAndVisible()
    28        }
    29    }
    30
    31    func sceneDidDisconnect(_ scene: UIScene) {
    32        // Called as the scene is being released by the system.
    33        // This occurs shortly after the scene enters the background, 
    34        // or when its session is discarded.
    35        // Release any resources associated with this scene that can be re-created 
    36        // the next time the scene connects.
    37        // The scene may re-connect later, as its session was not necessarily 
    38        // discarded (see `application:didDiscardSceneSessions` instead).
    39    }
    40
    41    func sceneDidBecomeActive(_ scene: UIScene) {
    42        // Called when the scene has moved from an inactive state to an active state.
    43        // Use this method to restart any tasks that were paused (or not yet started) 
    44        // when the scene was inactive.
    45    }
    46
    47    func sceneWillResignActive(_ scene: UIScene) {
    48        // Called when the scene will move from an active state to an inactive state.
    49        // This may occur due to temporary interruptions (ex. an incoming phone call).
    50    }
    51
    52    func sceneWillEnterForeground(_ scene: UIScene) {
    53        // Called as the scene transitions from the background to the foreground.
    54        // Use this method to undo the changes made on entering the background.
    55    }
    56
    57    func sceneDidEnterBackground(_ scene: UIScene) {
    58        // Called as the scene transitions from the foreground to the background.
    59        // Use this method to save data, release shared resources, and store enough 
    60        // scene-specific state information
    61        // to restore the scene back to its current state.
    62    }
    63}

Xcode 12.4 and Xcode 13:

  1. At the top of your project’s SceneDelegate class, be sure that you’ve declared a UIWindow variable:
    1class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    2
    3    var window: UIWindow?
  2. At the bottom of the SceneDelegate class body, implement a private function named resetViewState(_:) that takes an escaping closure as its sole parameter. This function dismisses the current root view controller and then executes the given closure:
    1func resetViewState(_ postResetBlock: @escaping () -> ()) {
    2    if let rootViewController = self.window?.rootViewController {
    3        if let _ = rootViewController.presentedViewController {
    4            rootViewController.dismiss(animated: false, 
    5                completion: postResetBlock)
    6            return
    7        }
    8        self.window?.rootViewController = nil
    9    }
    10    postResetBlock()
    11}
  3. Implement a private function named setupRootViewController() that launches your app’s custom views. For this simple exercise, you can set the root view to the SwiftUI ContentView provided by the Xcode template.
    1func setupRootViewController() {
    2    self.window?.rootViewController = UIHostingController(rootView: 
    3        ContentView())
    4}

    The Xcode template’s ContentView requires no additional setup or cleanup. For a more elaborate example, see setupRootViewController(_:) in the MobileSyncExplorerSwift template.

    Note

  4. In the scene(_:willConnectTo:options:) function, provide code that sets up the window scene. Replace all existing code with the following lines:
    1func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 
    2    options connectionOptions: UIScene.ConnectionOptions) {
    3
    4    guard let windowScene = (scene as? UIWindowScene) else { return }
    5    self.window = UIWindow(frame: windowScene.coordinateSpace.bounds)
    6    self.window?.windowScene = windowScene
    7    
    8
    9}
  5. Register a callback block for user change events. Using the private functions you added, provide a closure that resets the view state and then presents your app’s root view.
    1func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 
    2    options connectionOptions: UIScene.ConnectionOptions) {
    3
    4    guard let windowScene = (scene as? UIWindowScene) else { return }
    5    self.window = UIWindow(frame: windowScene.coordinateSpace.bounds)
    6    self.window?.windowScene = windowScene
    7
    8    // Define a response to user change events
    9    AuthHelper.registerBlock(forCurrentUserChangeNotifications: {
    10        self.resetViewState {
    11            self.setupRootViewController()
    12        }
    13    })
    14}
  6. Implement a private function named initializeAppViewState() that sets InitialViewController as the root view. Notice that this function first makes sure that the app is on the main UI thread. If the app is on some other thread, it switches to the main thread and calls itself recursively.
    1func initializeAppViewState() {
    2    if (!Thread.isMainThread) {
    3        DispatchQueue.main.async {
    4            self.initializeAppViewState()
    5        }
    6        return
    7    }
    8
    9    self.window?.rootViewController = 
    10        InitialViewController(nibName: nil, bundle: nil)
    11    self.window?.makeKeyAndVisible()
    12}
  7. In the sceneWillEnterForeground(_:) listener function:
    1. To set the root view to InitialViewController, call initializeAppViewState().
    2. Call AuthHelper.loginIfRequired(_:) with a closure that sets the root view controller to your app’s main view.
    1func sceneWillEnterForeground(_ scene: UIScene) {
    2    // Called as the scene transitions from the background to the foreground.
    3    // Use this method to undo the changes made on entering the background.
    4    self.initializeAppViewState()
    5    AuthHelper.loginIfRequired {
    6        self.setupRootViewController()
    7    }
    8}

Build Your New Mobile SDK App

  1. In Xcode, select Product | Run (⌘R).
  2. If the Salesforce login screen appears, you’re done!
Your new project is ready for customization. You can now add your own assets and set up your scene flow. You can also add optional push notification and IDP features, as follows.

Another reminder! Before posting your app to the App Store, be sure to update the remoteAccessConsumerKey and oauthRedirectURI in the bootconfig.plist file with settings from your connected app. See Get Started with Native iOS Development.

Important

Add Push Notification Support

  1. To support Salesforce push notifications, add code to register and handle them. You can copy boilerplate implementations of the following methods from the AppDelegate class of the Mobile SDK iOSNativeSwiftTemplate app:
    • registerForRemotePushNotifications()
    • application(_:didRegisterForRemoteNotificationsWithDeviceToken:)Be sure to uncomment the one line of implementation code!
    • didRegisterForRemoteNotifications(_:)
    • application(_:didFailToRegisterForRemoteNotificationsWithError:)
  2. Salesforce uses encryption on notifications, which requires you to implement a decryption extension and then to request authorization through the UNUserNotificationCenter object. See Code Modifications (iOS).

Add Identity Provider Support

Mobile SDK supports configuration of identity provider (IDP) apps. See Identity Provider Apps. If you’ve implemented IDP functionality, you enable it by customizing AppDelegate and SceneDelegate.

  1. In your AppDelegate class, add the following boilerplate functions from the AppDelegate class of the Mobile SDK iOSNativeSwiftTemplate app:
    • application(_:open:_:)
    • enableIDPLoginFlowForURL(_:_:)
  2. In your SceneDelegate class, add the following functions.
    1func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    2    if let urlContext = URLContexts.first {
    3        self.enableIDPLoginFlowForURLContext(urlContext, scene: scene)
    4    }
    5}
    6
    7func enableIDPLoginFlowForURLContext(_ urlContext: UIOpenURLContext, 
    8    scene: UIScene) -> Bool {
    9    return UserAccountManager.shared.handleIdentityProviderResponse(
    10        from: urlContext.url, with: [UserAccountManager.IDPSceneKey: 
    11            scene.session.persistentIdentifier])
    12}

Customize the Pre-Login Screen

You can use the InitialViewController class to cosmetically customize the login view before the login screen appears. Here’s an example that brands the pre-login screen with your app’s name, using the default Salesforce background color. If you use this class to create resources that can be recreated, be sure to implement the didReceiveMemoryWarning() override to dispose of them. See the Xcode UIViewController documentation.
1import Foundation
2import UIKit
3import SalesforceSDKCore.UIColor_SFColors
4
5class InitialViewController: UIViewController {
6
7    override func viewDidLoad() {
8        super.viewDidLoad()
9        self.view.backgroundColor = UIColor.salesforceSystemBackground
10        let label = UILabel()
11        label.translatesAutoresizingMaskIntoConstraints = false
12        guard let info = Bundle.main.infoDictionary, 
13            let name = info[kCFBundleNameKey as String] else { return }
14        label.font = UIFont.systemFont(ofSize: 29)
15        label.textColor = UIColor.black
16        label.text = name as? String
17        self.view.addSubview(label)
18        label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).
19            isActive = true
20        label.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).
21            isActive = true
22        // Do any additional setup after loading the view, 
23        // typically from a nib.
24    }
25
26    override func didReceiveMemoryWarning() {
27        super.didReceiveMemoryWarning()
28        // Dispose of any resources that can be recreated.
29    }
30}

Include SmartStore and Mobile Sync Configuration Files

Offline configuration files let you define your SmartStore and Mobile Sync setups in static XML files instead of code. These files must be named userstore.json and usersyncs.json and be referenced in your Xcode project. You can then load them by calling the following methods:
1// Setup store based on config userstore.json
2MobileSyncSDKManager.shared.setupUserStoreFromDefaultConfig()
3// Setup syncs based on config usersyncs.json
4MobileSyncSDKManager.shared.setupUserSyncsFromDefaultConfig()
Your main consideration for these files is to be sure to load them only one time in each session. In a SceneDelegate flow, for example, loads the config files when
  • Salesforce login is required, or
  • When a change of users occurs
The Mobile SDK native Swift template keeps things simple by calling the two setup functions whenever it sets the root view controller. This task falls to a private function, setupRootViewController(), that in turn is called as follows:
  • In sceneWillEnterForeground(_:), where it’s called in a block that runs after a Salesforce login occurs.
  • In scene(_:willConnectTo:options:), where it’s called in a block that handles current user change notifications.
Mobile SDK defines a utility class, AuthHelper, that coordinates the internal moving parts for login and user change events. For a complete example, see the SceneDelegate class in the Mobile Sync Explorer Swift template.

If your app defers login, where and when you call AuthHelper.loginIfRequired(_:) is up to you. In all cases, be sure to precede the call by setting the root view controller to IntialViewController, as shown here.

Note