SwiftlySalesforce

8.0.1

The Swift-est way to build native mobile apps that connect to Salesforce.
mike4aday/SwiftlySalesforce

What's New

8.0.1

2020-07-10T21:47:12Z

Minor bug fixes, and updated default Salesforce API version to 48.0 (Spring '20).

  

Build iOS apps fast on the Salesforce Platform with Swiftly Salesforce:

  • Written entirely in Swift.
  • Uses Swift's new Combine framework to simplify complex , asynchronous Salesforce API interactions.
  • Works with SwiftUI
  • Manages the Salesforce OAuth2 user authentication and authorization process (the "OAuth dance") automatically.
  • Simpler and lighter alternative to the Salesforce Mobile SDK for iOS.
  • Easy to install and update with Swift Package Manager (SPM)
  • Compatible with Realm for a complete, offline mobile solution.
  • See what's new.

Quick Start

You can be up and running in a few minutes by following these steps:

  1. Get a free Salesforce Developer Edition
  2. Create a Salesforce Connected App in your new Developer Edition
  3. Add Swiftly Salesforce to your Xcode project as a package dependency, with URL https://github.com/mike4aday/SwiftlySalesforce.git.

Minimum Requirements

  • iOS 13.0
  • Swift 5.1
  • Xcode 11

Examples

Below are some examples that illustrate how to use Swiftly Salesforce. Swiftly Salesforce will automatically manage the entire Salesforce OAuth2 process (the "OAuth dance"). If Swiftly Salesforce has a valid access token, it will include that token in the header of every API request. If the token has expired, and Salesforce rejects the request, then Swiftly Salesforce will attempt to refresh the access token, without bothering the user to re-enter the username and password. If Swiftly Salesforce doesn't have a valid access token, or is unable to refresh it, then Swiftly Salesforce will direct the user to the Salesforce-hosted login form.

Example: Setup

You can create a re-usable reference to Salesforce in your SceneDelegate.swift file:

import UIKit
import SwiftUI
import SwiftlySalesforce
import Combine

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    var salesforce: Salesforce!

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        //...
        // Copy consumer key and callback URL from your Salesforce connected app definition
        let consumerKey = "<YOUR CONNECTED APP'S CONSUMER KEY HERE>"
        let callbackURL = URL(string: "<YOUR CONNECTED APP'S CALLBACK URL HERE>")!
        let connectedApp = ConnectedApp(consumerKey: consumerKey, callbackURL: callbackURL)
        salesforce = Salesforce(connectedApp: connectedApp)
    }
    
    //...
}

In the example above, we created a Salesforce instance with the Connected App's consumer key and callback URL. salesforce is an implicitly-unwrapped, optional, global variable, but you could also inject a Salesforce instance into your root view controller, for example, instead of using a global variable.

Example: Retrieve Salesforce Records

The following will retrieve all the fields for an account record:

salesforce.retrieve(type: "Account", id: "0013000001FjCcF")

To specify which fields should be retrieved:

let fields = ["AccountNumber", "BillingCity", "MyCustomField__c"]
salesforce.retrieve(type: "Account", id: "0013000001FjCcF", fields: fields)

Note that retrieve is an asynchronous function, whose return value is a Combine publisher:

let pub: AnyPublisher<SObject, Error> = salesforce.retrieve(type: "Account", id: "0013000001FjCcF")

And you could use the sink subscriber to handle the result:

let subscription = salesforce.retrieve(object: "Account", id: "0013000001FjCcF")
.sink(receiveCompletion: { (completion) in
    switch completion {
    case .finished:
        print("Done")
    case let .failure(error):
        //TODO: handle the error
        print(error)
    }
}, receiveValue: { (record) in
    //TODO: something more interesting with the result
    if let name = record.string(forField: "Name") {
        print(name)
    }
})

You can retrieve multiple records in parallel, and wait for them all before proceeding:

var subscriptions = Set<AnyCancellable>()
//...
let pub1 = salesforce.retrieve(object: "Account", id: "0013000001FjCcF")
let pub2 = salesforce.retrieve(object: "Contact", id: "0034000002AdCdD")
let pub3 = salesforce.retrieve(object: "Opportunity", id: "0065000002AdNdH")
pub1.zip(pub2, pub3).sink(receiveCompletion: { (completion) in
    //TODO:
}) { (account, contact, opportunity) in
    //TODO
}.store(in: &subscriptions)

Example: Custom Model Objects

Instead of using the generic SObject, you could define your own model objects. Swiftly Salesforce will automatically decode the Salesforce response into your model objects, as long as they implement Swift's Decodable protocol:

struct MyAccountModel: Decodable {

    var id: String
    var name: String
    var createdDate: Date
    var billingAddress: Address?
    var website: URL?

    enum CodingKeys: String, CodingKey {
        case id = "Id"
        case name = "Name"
        case createdDate = "CreatedDate"
        case billingAddress = "BillingAddress"
        case website = "Website"
    }
}

//...
let pub: AnyPublisher<MyAccountModel, Error> = salesforce.retrieve(object: "Account", id: "0013000001FjCcF")

Example: Update a Salesforce Record

salesforce.update(object: "Task", id: "00T1500001h3V5NEAU", fields: ["Status": "Completed"])
    .sink(receiveCompletion: { (completion) in
        //TODO: handle completion
    }) { _ in
        //TODO: successfully updated
    }.store(in: &subscriptions)

You could also use the generic SObject (typealias for SwiftlySalesforce.Record) to update a record in Salesforce. For example:

// `account` is an SObject we retrieved earlier...
account.setValue("My New Corp.", forField: "Name")
account.setValue(URL(string: "https://www.mynewcorp.com")!, forField: "Website")
account.setValue("123 Main St.", forField: "BillingStreet")
account.setValue(nil, forField: "Sic")
salesforce.update(record: account)
    .sink(receiveCompletion: { (completion) in
        //TODO: handle completion
    }) { _ in
        //TODO: successfully updated
    }
    .store(in: &subscriptions)

Example: Query Salesforce

let soql = "SELECT Id,Name FROM Account WHERE BillingPostalCode = '10024'"
salesforce.query(soql: soql)
    .sink(receiveCompletion: { (completion) in
        //TODO: completed
    }) { (queryResult: QueryResult<SObject>) in
        for record in queryResult.records {
            if let name = record.string(forField: "Name") {
                print(name)
            }
        }
    }
    .store(in: &subscriptions)

Example: Decode Query Results as Your Custom Model Objects

You can easily perform complex queries, traversing object relationships, and have all the results decoded automatically into your custom model objects that implement the Decodable protocol:

struct Account: Decodable {

    var id: String
    var name: String
    var lastModifiedDate: Date

    enum CodingKeys: String, CodingKey {
        case id = "Id"
        case name = "Name"
        case lastModifiedDate = "LastModifiedDate"
    }
}

struct Contact: Decodable {

    var id: String
    var firstName: String
    var lastName: String
    var createdDate: Date
    var account: Account?

    enum CodingKeys: String, CodingKey {
        case id = "Id"
        case firstName = "FirstName"
        case lastName = "LastName"
        case createdDate = "CreatedDate"
        case account = "Account"
    }
}

func getContactsWithAccounts() -> () {
    let soql = "SELECT Id, FirstName, LastName, CreatedDate, Account.Id, Account.Name, Account.LastModifiedDate FROM Contact"
    salesforce.query(soql: soql)
        .sink(receiveCompletion: { (completion) in
            //TODO: completed
        }) { (queryResult: QueryResult<Contact>) in
            for contact in queryResult.records {
                print(contact.lastName)
                if let account = contact.account {
                    print(account.name)
                }
            }
        }
        .store(in: &subscriptions)
    }

Example: Retrieve Object Metadata

If, for example, you want to determine whether the user has permission to update or delete a record so you can disable editing in your UI, or if you want to retrieve all the options in a picklist, rather than hardcoding them in your mobile app, then call salesforce.describe(type:) to retrieve an object's metadata:

salesforce.describe(object: "Account")
    .sink(receiveCompletion: { (completion) in
        //TODO: completed
    }) { (acctMetadata) in
        //TODO: update UI
        let editable = acctMetadata.isUpdateable
    }
    .store(in: &subscriptions)

Example: Log Out

If you want to log out the current Salesforce user, and then clear any locally-cached data, you could call the following. Swiftly Salesforce will revoke and remove any stored credentials.

let pub: AnyPublisher<Void, Error> = salesforce.logOut()
//TODO: Connect to UI element with SwiftUI

Example: Search with Salesforce Object Search Language (SOSL)

Read more about SOSL

let sosl = """
    FIND {"A*" OR "B*" OR "C*" OR "D*"} IN Name Fields RETURNING Lead(name,phone,Id), Contact(name,phone)
"""
salesforce.search(sosl: sosl)
    .sink(receiveCompletion: { (completion) in
        //TODO: completed
    }) { (results: [SObject]) in
        for result in results {
           print("Object type: \(result.object)")
        }
    }
    .store(in: &subscriptions)

Resources

If you're new to the Salesforce Platform or the Salesforce REST API, you might find the following resources useful:

Contact

Questions, suggestions, bug reports and code contributions welcome:

Description

  • Swift Tools 5.1.0
View More Packages from this Author

Dependencies

  • None
Last updated: Thu Oct 24 2024 07:55:13 GMT-0900 (Hawaii-Aleutian Daylight Time)