Native Swift Template

The native iOS Swift template supports the latest Swift features:
  • SwiftUI
  • Combine Publisher
  • SceneUI and SceneDelegate flow
Also, this template demonstrates Mobile SDK offline features.

This template defines two initialization classes: AppDelegate and SceneDelegate. These classes interact to enable a dynamic arrangement of multiple windows.

Architecturally, this template uses strict model-view separation, with one model class per view class. Model classes implement the ObservableObject protocol, while view classes implement the View protocol.

AppDelegate

AppDelegate manages high-level setup—app launch and the UISceneSession lifecycle. In Mobile SDK terms, AppDelegate in this template
  • Initializes Mobile SDK
  • Registers and unregisters push notification services, if implemented
  • Sets up IDP, if implemented
As in earlier Mobile SDK templates, you can customize settings in the application(_:didFinishLaunchingWithOptions:) method of AppDelegate.

SceneDelegate

SceneDelegate handles the life cycles of scenes in the UISceneSession container. The following initializer method configures a scene and adds it to the scene collection.
1func scene(_:UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
After instantiating the scene as a UIWindow, this method registers a handler for current user changes. If the CurrentUserChange event occurs, the template resets the view state by setting up the root view controller.
1AuthHelper.registerBlock(forCurrentUserChangeNotifications: {
2    self.resetViewState {
3               self.setupRootViewController()
4    }
5})
Later, in the sceneDidBecomeActive(_:) method, the template reinitializes the app view state and resets the root view controller. If no user is logged into Salesforce, the template requires the user to log in before it performs this reset.
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}

Model Classes

Models for the template’s views differ in how they download Salesforce information.
  • The AccountsListModel class uses the Mobile Sync SyncManager publisher to sync down information for all accounts. When the response arrives, the publisher stores the information in a SmartStore soup, according to the sync and SmartStore configurations.
    1func fetchAccounts(){
    2    _ = syncManager?.publisher(for: "syncDownAccounts")
    3        .receive(on: RunLoop.main)
    4        .sink(receiveCompletion: { _ in }, receiveValue: { _ in
    5            self.loadFromSmartStore()
    6        })
    7    
    8    self.loadFromSmartStore()
    9}
    To send account information to the view, the loadFromSmartStore() method uses the SmartStore publisher to extract the data from the SmartStore soup.
    1private func loadFromSmartStore(){
    2    storeTaskCancellable = 
    3        self.store?.publisher(for: "select {Account:Name}, 
    4        {Account:Industry}, {Account:Id} from {Account}")
    5        .receive(on: RunLoop.main)
    6        .tryMap{
    7            $0.map { (row) -> Account in
    8                let r = row as! [String?]
    9                
    10                return Account(id: r[2] ?? "", name: r[0] ?? "",
    11                    industry: r[1] ?? "Unknown Industry" )
    12        }
    13    }
    14    .catch { error -> Just<[Account]> in
    15        print(error)
    16        return Just([Account]())
    17    }
    18    .assign(to: \AccountsListModel.accounts, on:self)
    19}
  • The ContactsForAccountModel class uses a RestClient factory method to create the REST request.
    1let request = RestClient.shared.request(forQuery: "SELECT id, firstName, lastName, phone, email, mailingStreet, mailingCity, mailingState, mailingPostalCode FROM Contact WHERE AccountID = '\(acct.id)'", apiVersion: nil)
    To send a request to Salesforce and process the asynchronous response, this model uses the RestClient shared Combine publisher.
    1contactsCancellable = RestClient.shared.publisher(for: request)
    2    .receive(on: RunLoop.main)
    3    .tryMap({ (response) -> Data in
    4        response.asData()
    5    })
    6    .decode(type: ContactResponse.self, decoder: JSONDecoder())
    7    .map({ (record) -> [Contact] in
    8        record.records
    9    })
    10    .catch( { error in
    11        Just([])
    12    })
    13    .assign(to: \.contacts, on:self)
    14...
    This model stores details for all displayed contacts in a published array. See Handling REST Requests.

View Classes

Views include a list of accounts, and list and detail views of related contacts. The Account list view displays a list of account names. Selecting an account brings up a list view of the account’s contacts. When the customer selects a contact record, a detail view displays the contact’s information.
  • Accounts
    • Account | Contacts
      • Contact | Details
Views serve to configure a visual interface that imports and presents data from the related model object. To handle taps on view items, the view object sets up a navigation link to the destination view and passes it the necessary data. Here’s the definition for the Accounts list view.
1struct AccountsListView: View {
2  @ObservedObject var viewModel = AccountsListModel()
3  
4  var body: some View {
5    NavigationView {
6      List(viewModel.accounts) { dataItem in
7        NavigationLink(destination: 
8            ContactsForAccountListView(account: dataItem)){
9          HStack(spacing: 10) {
10            VStack(alignment: .leading, spacing: 3) {
11              Text(dataItem.name)
12              Text(dataItem.industry).font(.subheadline).italic()
13            }
14          }
15        }
16      }
17      .navigationBarTitle(Text("Accounts"), displayMode: .inline)
18    }
19    .onAppear{ self.viewModel.fetchAccounts() }
20  }
21}