The swiftest way to build iOS apps that connect to Salesforce

Overview

  

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

  • Written entirely in Swift.
  • Uses Swift's 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 Core Data 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:

Comments
  • 'group by' count() soql fails in 9.0

    'group by' count() soql fails in 9.0

    SELECT activitydate, status, Count(Id) Cnt FROM task WHERE isdeleted = false GROUP BY activitydate, status

    used to work, now it fails with : Error: keyNotFound(CodingKeys(stringValue: "url", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "records", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), RecordCodingKey(stringValue: "attributes", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "url", intValue: nil) ("url").", underlyingError: nil))

    bug 
    opened by perbrondum 26
  • I am using below custom API and getting below error:

    I am using below custom API and getting below error:

    The operation could not be completed. (SwiftlySalesforce.RequestError error 1.)

    Using: first { salesforce.apex(method: .post, path: path!, parameters: nil, body: jsonData, contentType: "application/json") }.then { result in }.catch { error in }

    question 
    opened by degreecloud 18
  • Salesforce login Page is not showing up

    Salesforce login Page is not showing up

    I have added swiftly salesforce through cocoa pods and successfully configured app delegate file but instead of opening salesforce login page on safari, app shows my rootViewcontroller. Please suggest what i should try?

    question 
    opened by degreecloud 15
  • Salesforce.identity fails when iOS time setting is a specific condition.

    Salesforce.identity fails when iOS time setting is a specific condition.

    Salesforce.identity() fails when iOS Setting Time 24-Hour Time is off ( iOS displays am/pm time when the mode is off) .

    This error doesn't occur iOS simulators and when iOS language is English. iOS language was Japanese when the error occcured.

    The URL below is a related document. https://developer.apple.com/library/content/qa/qa1480/_index.html

    struct Identity {
      public init(from decoder: Decoder) throws {
        ....
        // failed.
        self.lastModifiedDate = try container.decode(Date.self, forKey: .lastModifiedDate) 
      } 
    }
    
    investigating 
    opened by hmuronaka 13
  • Custom Branding login page not supported

    Custom Branding login page not supported

    • Login working fine when we using default login page • When we use custom branding login page • It loads the login url in SFSafariViewController properly, but after click on login its redirects me to chatter page in same window, which is incorrect flow. • In case of default login page, it redirects me to my application, which is correct flow. • For custom branding we are using vfx page in backend. • I need to implement custom branding login in my application and navigate to my application after click on login Kindly can you guys help me with this. Thanks in advance ...

    question investigating 
    opened by abhijeetkokane 10
  • Treat 403 errors differently when due to lack of API support or API request limit exceeded

    Treat 403 errors differently when due to lack of API support or API request limit exceeded

    For example, 403 error caused by Professional Edition without API should be handled differently (e.g. error exposed to end user) than other causes, which result in re-authentication and re-authorization via Salesforce-hosted form. See also Issue #27

    bug 
    opened by mike4aday 10
  • How to use SFPushNotificationManager in Swiftly

    How to use SFPushNotificationManager in Swiftly

    So I'm able to send a test push to the device from the Salesforce console... However, I believe I'm meant to use the SFPushNotificationManager to get the device token into the User object in salesforce.

    Is SFPushNotificationManager not available in SwiftlySalesforce. Will I have to use and auth with the SalesforceMobileSDK-iOS in order to do this?

    question 
    opened by helloniklas 10
  • organization() does not return organization info

    organization() does not return organization info

    Should the following not work? (a la identity()) first {
    salesforce.organization() }.then { (org) -> Promise in if let nameSpacePrefix = org.nameSpacePrefix { print("using namespaceprefix (nameSpacePrefix)") } }

    question 
    opened by ghost 9
  • Not able to open another URL of SF after opening login URL on Safari controller

    Not able to open another URL of SF after opening login URL on Safari controller

    Hi,

    I'm using swiftly salesforce to for login to salesforce account from my iPhone app using Swift 3.0. I have followed this SwiftlySalesforce link.

    Here swiftly salesforce replaces your rootviewController with SFSafariViewController. Once login is done it will replace safari view controller with your initial rootviewController. I'm able to get all details of user from which I have logged in. Now on my initial view which is my rootview, I want to show chatter page which usually comes after successful login on webview. But I'm getting login page again on webview.

    I think this is happening because the login seesion I'm getting from safari View controller is not getting to webview. Or is there any other issue?

    If anyone have worked on such requirement, or anyone could help me, please post your answers here. Your help is appritiated.

    Thank you, Ankita

    question 
    opened by NanostuffsTech 9
  • Authorization Not Working

    Authorization Not Working

    I am completely unsure why authorization is not working for me..

    Not working with example app or my integrated app.

    I am taken to the callbackURL, but then after that the safari view controller is not dismissed and the app does not retrieve the token.

    question 
    opened by aparente99 9
  • Query with date predicate fail

    Query with date predicate fail

    Simple Date SOQL statement fails in 9.0.6 "select name from account where lastmodifiedDate > 2023-01-01T12:00:00.000+0100"

    fails with: "Query Error : SalesforceError(code: "MALFORMED_QUERY", message: "\nlastmodifiedDate > 2023-01-01T12:00:00.000 0100\n ^\nERROR at Row:2:Column:48\nline 2:48 no viable alternative at character ' '", fields: nil)"

    testcase: `// Fails in 9.06 BETA with date format @available(iOS 15.0.0, *) private func simpleDateQuery() async throws { DB.salesforce.identity().sink(receiveCompletion: { (completion) in switch completion { case .finished: break case let .failure(error) : print("Failed to login. Error: (error)") } }) { identity in let userId = identity.userID

        let userSoql = "select name from account where lastmodifiedDate > 2023-01-01T12:00:00.000+0100"
      //  print(userSoql)
        DB.salesforce.query(soql: userSoql).sink(receiveCompletion: { (completion) in
            switch completion {
            case let .failure(error):
                testResults[8] = "simpleDateQuery, Failure"
                testErrors[8] = error
                print("Failed query(8) SFDC. Error: \(error)")
            case .finished:
                testResults[8] = "simpleDateQuery, Success"
                printOut()
            }
        }, receiveValue: { (userData : QueryResult<User>) in
            //
        }).store(in: &subscriptions)
    }.store(in: &subscriptions)
    

    }`

    bug 
    opened by perbrondum 8
Releases(v10.0.1)
Owner
Michael Epstein
@salesforce
Michael Epstein
Google Directions API helper for iOS, written in Swift

PXGoogleDirections Google Directions API SDK for iOS, entirely written in Swift. ?? Features Supports all features from the Google Directions API as o

Romain L 268 Aug 18, 2022
Swifter - A Twitter framework for iOS & OS X written in Swift

Getting Started Installation If you're using Xcode 6 and above, Swifter can be installed by simply dragging the Swifter Xcode project into your own pr

Matt Donnelly 2.4k Dec 26, 2022
CovidCertificate SDK for iOS

This is the Swiss implementation of the Electronic Health Certificates (EHN) Specification [1] used to verify the validity of Digital Covid Certificates. It is partly based on the reference implementation of EHN's ValidationCore

Swiss Admin 19 Apr 5, 2022
Backport of iOS 15 formatting api

This is a back-port of the .formatted API in Foundation that was introduced at WWDC '21 for iOS 15, macOS 12.0, tvOS 15.0, and watchOS 8.0.

Simon Salomons 9 Jul 22, 2022
App iOS correspondiente al proyecto twitimer.com de la comunidad MoureDev

⏳ Twitimer iOS Twitimer es una App gratuita para iOS y Android que se ha desarrollado para ayudar a usuarios de Twitch, pero sobre todo pensando en ge

Brais Moure 220 Jan 1, 2023
Unofficial iOS/macOS SDK for the Notion API.

NotionClient: a Notion SDK for iOS & macOS Unofficial Notion API SDK for iOS & macOS. This is an alpha version and still work in progress. TODO Featur

David De Bels 15 Aug 4, 2022
Desk360 Mobile Chat SDK for iOS

Desk360 Chat iOS SDK Desk360 Chat SDK provides simplicity and usability in one place. With this feature, you can provide live support to your customer

null 5 Sep 21, 2022
WANNA SDK enhances your iOS app with virtual try-on capabilities for shoes and watches

WANNA SDK enhances your iOS app with virtual try-on capabilities for shoes and watches. With this feature, your users will be able to see in real time how the selected product looks on them, just by pointing their smartphone camera at their feet or wrist.

Wannaby Inc. 18 Dec 2, 2022
The Swift-est way to build native mobile apps that connect to Salesforce.

Swiftly Salesforce is the Swift-est way to build native mobile apps that connect to Salesforce: Written entirely in Swift. Very easy to install and up

Michael Epstein 131 Nov 23, 2022
Watchbuild - Get a notification once your iTunes Connect build is finished processing

fastlane WatchBuild Get a notification once your App Store Connect build is finished processing When you upload a new binary from Xcode to App Store C

fastlane 324 Dec 14, 2022
Zilla connect is an easy, fast and secure way for your users to buy now and pay later from your app

Zilla Checkout iOS SDK Zilla connect is an easy, fast and secure way for your us

null 0 Jan 19, 2022
A way to build TUI apps with a layout system and API that's similar to SwiftUI.

terminal-ui A way to build TUI apps with a layout system and API that's similar to SwiftUI. We reimplemented parts of the SwiftUI layout system in the

Chris Eidhof 316 Dec 29, 2022
IOS-PokemonQuizApp - Assignment to make a responsive iOS app. App has to connect with an external API

iOS-PokemonQuizApp Assignment to make a responsive iOS app. App has to connect with an external API. The Project The idea of the project is to make a

BennyDB 0 Jan 9, 2022
Jorge Ovalle 305 Oct 11, 2022
Native iOS port of KDE Connect

Welcome to KDE Connect iOS 2021's Testing Repository! This project is inteded to be the iOS version of the group of applications called KDE Connect, w

KDE GitHub Mirror 292 Dec 27, 2022
The Waterwheel Swift SDK provides classes to natively connect iOS, macOS, tvOS, and watchOS applications to Drupal 7 and 8.

Waterwheel Swift SDK for Drupal Waterwheel makes using Drupal as a backend with iOS, macOS, tvOS, or watchOS enjoyable by combining the most used feat

Kyle Browning 414 Jul 26, 2022
This is a simple mobile app which is connect to the Twitter API

Project 3 - My Twitter My Twitter is a basic twitter app to read your tweets. Time spent on two parts: 8.5 hours spent in total Twitter - Part II This

Alem 1 Dec 14, 2022
This is a simple app, which scans for BLE Peripherials and connect to them. The example works with NORDIC_UART_SERVICE.

BLE Serial IOs Example This is a simple app, which scans for BLE Peripherials and connect to them. The example works with NORDIC_UART_SERVICE. UUIDS H

Muhammad Hammad 4 May 10, 2022
Swift framework to connect SMB2/3 shares

AMSMB2 This is small Swift library for iOS, macOS and tvOS which wraps libsmb2 and allows to connect a SMB2/3 share and do file operation. Install Coc

Amir Abbas Mousavian 157 Dec 19, 2022
Elegantly connect to a JSON api. (Alamofire + Promises + JSON Parsing)

⚠ Important Notice: Farewell ws... hello Networking ! Networking is the next generation of the ws project. Think of it as ws 2.0 built for iOS13. It u

Fresh 351 Oct 2, 2022