iOS SDK

Fork it at https://github.com/pactsafe/pactsafe-ios-sdk

iOS SDK

Requirements

  • Xcode 11 or higher
  • Target iOS 10.0 or higher
  • Swift 5.0+
  • PactSafe Published Contracts in Public Group
  • PactSafe Group Key
  • PactSafe Site Access ID
  • PactSafe API Access

Installation

Swift Package Manager

You can use the Swift Package Manager to install the PactSafe SDK:

https://github.com/pactsafe/pactsafe-ios-sdk.git

CocoaPods

You can use CocoaPods to install the PactSafe SDK by adding it to your Podfile:

platform :ios, '10.0'
use_frameworks!

target 'MyApp' do
	pod 'PactSafe', '~> 1.0.1'
end

Carthage

You can use Carthage to install the PactSafe SDK by adding it to your Cartfile:

github "pactSafe/pactsafe-ios-sdk" ~> 1.0.1

GitHub

Use the GitHub repo to download the entire framework manually.

Notes Before Getting Started

Demo iOS App

As you follow along in this guide, you may want to look at the PactSafe iOS Demo App as an example. You can pull down the demo app here in GitHub.

Debug Mode

Something not quite working the way you expect or you need additional information as to what might not be working? Simply enable the debugMode property on PSApp.shared to print additional information.

PSApp.shared.debugMode = true

Test Mode

Optionally, set testMode to true as you are testing your implementation. This allows you to delete test data in your PactSafe site.

Note: Don't forget to remove this line before you are finished!

PSApp.shared.testMode = true

Data Types

Before you start to implement, you will want to become familiar with a few data types used by the iOS SDK.

NameDescription
PSSignerIDPSSignerID is a typealias for String.
PSSignerPSSigner is a structure that you'll use to send over your signer information. You must include a signer ID (PSSignerID) when needing to send data to PactSafe. You can optionally pass over additional custom data with a PSCustomData struct, which is covered below.
PSCustomDataPSCustomData is a structure that holds additional information about the activity. Please see the properties that are available to be set in the Customizing Acceptance Data section.
PSGroupPSGroup is a structure that holds information about a speciifc group (uses PactSafe group key) that is loaded from the PactSafe API.
PSContractPSContract is a structure that holds information about contracts within a PactSafe group (PSGroup).
PSConnectionDataThe PSConnectionData structure holds information about the current connection Customizing Acceptance Data section.

Configure and Initalize the PactSafe SDK

In order to use the PactSafe SDK, you’ll need to import PactSafe into your UIApplicationDelegate:

import PactSafe

Using the SDK also requires authentication, which you’ll want to set up in your application:didFinishLaunchingWithOptions delegate.

Note: You must configure your PactSafe Site Access ID before using the PSApp shared instance!

PSApp.shared.configure(siteAccessId: "yourSiteAccessId")

PSClickWrapView

The easiest way of getting started with using the PactSafe clickwrap is by utilizing our PSClickWrapView class to dynamically load your contracts into a UIView. The PSClickWrapView class conforms to the UIView class, which allows you to easily customize and format the clickwrap as needed.

Preloading Clickwrap Data

Since your PSClickWrapView class will load contracts for the specified PactSafe group, you may want to preload the data using your group key before displaying the clickwrap. By preloading, a user will be less likely see loading when they get to the screen that contains the PSClickWrapView.

To preload your PactSafe group data, you can use the preload method on the PSApp shared instance within your AppDelegate. Example below:

// Do this after you configure your PSApp shared instance!
PSApp.shared.preload(withGroupKey: "example-pactsafe-group-key")

By using the preload method, the data is stored using the iOS URLCache class in memory only.

Loading Your Clickwrap

The PSClickWrapView class is built on top of a UIView, which gives you flexibility in your implementation. You can implement in the following ways:

  • Interface Builder - add the custom class to a UIView within your storyboard that will load your PactSafe clickwrap.
  • Programmatically - programmatically gives you the most flexibility implementing the PactSafe clickwrap into your project.

Interface Builder

With an empty view in your storyboard, simply subclass the UIView with the PSClickWrapView class. Once you subclass the UIView, you’ll need to do some configuring of the ClickWrap within your view controller.

Note: Don't forget to import PactSafe into your view controller.

IBOutlet your Clickwrap

Make sure to create an IBOutlet to your PSClickWrapView UIView in order to customize it.

Loads Contracts Into Your Clickwrap

In order to get contracts to load into your clickwrap, you’ll need to use the loadContracts method, where you'll pass in your PactSafe group key.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    myClickWrap.loadContracts(withGroupKey: "example-mobile-app-group")
}

Once loaded, your clickwrap might look something like this:

Example Loaded Clickwrap

Programmatically

To use the PSClickWrapView class programmatically, you can use the default initializer that accept a frame with a CGRect size as you normally would while using UIView.

// Example of what might be in your UIViewController.
private var clickWrap: PSClickWrapView?

override func viewDidLoad() {
    super.viewDidLoad()
    configureClickWrap()
}

private func configureClickwrap() {
    clickWrap = PSClickWrapView(frame: CGRect.zero)
    guard let clickWrap = clickWrap else { return }
    clickWrap.loadContracts(withGroupKey: "your-group-key")
    // Insert your PSClickWrapView and do any additional setup.
}

Configure Contracts Link Tap Behavior

The PSClickWrapView loads the text and links into a UITextView, which gives you flexibility for customizing link tap behavior. By default, UITextView will take users out of your app and into Safari. If you'd rather keep users in your app, you can use a UITextViewDelegate to adjust the default behavior.

Import Safari Services

Within the view controller where you configured your clickwrap, you’ll need to import SafariServices.

import SafariServices
Configure PSClickWrapView UITextView Delegate

The PSClickWrapView contains a property textView that exposes the UITextView that holds your acceptance language and links to your terms.

// Assign the delegate to your view controller
myClickWrap.textView.delegate = self
Implement UITextViewDegate Protocol and Method

Having assigned the delegate to your ViewController, you’ll need to add the UITextViewDelegate to your ViewController and use the shouldInteractWith URL method.

extension MyViewController: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        let safariVc = SFSafariViewController(url: URL)
        present(safariVc, animated: true, completion: nil)
        return false
    }
}

Check if Checkbox is Selected

Before letting a user submit the form, you may want to make sure that the checkbox is selected. To do so, you can monitor the value of the checkbox when you configure your clickwrap.

// Can be used after you load your clickwrap (e.g., after you call loadContracts).
myClickWrap.checkbox.valueChanged = { (isChecked) in
    // If checked, enable (true) your UIButton submit button otherwise ensure it's disabled (false). You may want to also adjust the style of your button here as well.
    if isChecked {
    	self.yourSubmitButton.isEnabled = true
    } else {
    	self.yourSubmitButton.isEnabled = false
    }
}

Sending Acceptance

When using PSClickWrapView, you can easily send an "agreed" event once they have accepted your contracts. To do this, you'll pass along a PSSigner to the sendAgreed method on your PSClickWrapView class.

/// PSClickWrapView has a method 'sendAgreed' that allows you to easily send acceptance using a signer id and any custom data.
let signer = PSSigner(signerId: signerId, customData: customData)
myClickWrap.sendAgreed(signer: signer) { (response, error) in
    if error == nil {
        // Use PSCustomData to send additional data about the activity
        var customData = PSCustomData()
        customData.firstName = firstNameText
        customData.lastName = lastNameText

        // Create the signer with the specified id and custom data.
        let signer = PSSigner(signerId: emailAddressText, customData: customData)
                
        // Use the sendAgreed method on the clickwrap to send acceptance.
        self.pactSafeClickWrap.sendAgreed(signer: signer) { (error) in
            if error == nil {
                // Handle next step
            } else {
                // Handle error
            }
        }
    } else {
        // Handle error
    }
}

PSClickWrapViewDelegate

You can optionally use the PSClickWrapViewDelegate protocol to receive events for your PSClickWrapView. The following methods are available to be used:

Method DefinitionDescriptionOptional
clickWrapRendered(withGroup groupData: PSGroup)Triggered when a group has loaded and provides group data.No
errorLoadingGroup(error: Error?)Triggered when there's an error loading the group data.Yes

Checking Acceptance

We provide a few of ways checking acceptance and optionally presenting information if major version changes have been published. The following are three potential options you may choose to use:

  • Using the signedStatus method
  • Using a PSAcceptanceViewController
  • Using signedStatus Method and Present Alert

Using the signedStatus Method

The signedStatus method gives you the opportuntiy to check on the status of acceptance within a specific PactSafe group.

let signerId = "[email protected]"
let psGroupKey = "example-group-key"

// The signedStatus method will return a boolean of whether the specified signer id has accepted all contracts within the group key. If they do need to accept a more recent version, the IDs of contracts will be returned in an array [String].
ps.signedStatus(for: signerId, groupKey: psGroupKey) { (needsAcceptance, contractIds) in 
    if needsAcceptance {
        // Handle showing acceptance needed.
    } else {
        self.segueHome()
    }
}

Using the PSAcceptanceViewController

You can optionally choose to utilize the PSAcceptanceViewController in order to conveniently present to your users which contracts had major changes, what the changes were (if change summary is provided within PactSafe), and an opportunity to accept them.

What it Looks Like

We provide a simple implementation that can be easily customized to incorporate your brand styling. More on styling later in the documentation.

Example PSAcceptanceViewController

Setting It Up

// Set up your PSApp.shared instance for use.
let ps = PSApp.shared

// Set the PactSafe group key you plan to check.
let groupKey: String = "my-pactsafe-group-key"

/**
 * Use the signedStatus method to get acceptance information.
 * You'll pass in the signer id that you want to check for and your group key.
 * The method will return whether any acceptance is needed and the contract ids that need acceptance.
*/
ps.signedStatus(for: signerId, in: groupKey) { (needsAcceptance, contractIds) in
    if needsAcceptance {
        // Call the PSAcceptanceViewController with the group key, signer id, and contract ids that need to be accepted.
        let psAcceptanceVc = PSAcceptanceViewController(groupKey, signerId, contractIds)
        // Since PSAcceptanceViewController conforms to UIViewController, you can configure your presentation.
        psAcceptanceVc.modalPresentationStyle = .automatic
        psAcceptanceVc.modalTransitionStyle = .coverVertical
        self.present(psAcceptanceVc, animated: true, completion: nil)
    } else {
        // No acceptance is needed, so move them to where they should go.
        self.segueToHome()
    }
}

PSAcceptanceViewControllerDelegate

You can use the PSAcceptanceViewControllerDelegate to receive events associated with the PSAcceptanceViewController.

Available methods when you adopt to the protocol:

Method DefinitionDescriptionOptional
receivedAcceptance()Triggered when a successful submission of acceptance has been sent.Yes
errorSendingAcceptance(error: Error?)Triggered when there's an issue with sending acceptance.Yes
checkboxIsSelected(_ checked: Bool)Triggered when the checkbox is checked or unchecked.Yes
errorLoadingGroup(error: Error?)Triggered when there's an error loading the PactSafe group for the PSClickWrapView.Yes
Receive Notice of Acceptance

You'll probably want to know if the user checked the box and then clicked submit. You'll need to adopt a PSAcceptanceViewControllerDelegate protocol to your ViewController and implement the receivedAcceptance method to know acceptance was received. Implementation may look something like this:

extension MyViewController: PSAcceptanceViewControllerDelegate {
    func receivedAcceptance() {
        // Take your action here
        loginUser()
    }
}

Using signedStatus Method and Present Alert

You may want a more simple approach of presenting that acceptance is needed or need greater customization. To get details around acceptance status, we provide two methods signedStatus and loadGroup that help you get the appropriate information for displaying to a user.

/// Get the status for a specific signer in a group.
ps.signedStatus(for: signerId, groupKey: groupKey) { (needsAcceptance, contractIds) in
    if needsAcceptance {
        self.showContractUpdates(forSignerId: signerId, password: passwordText)
    } else {
        // Handle next step
    }
}

private func showContractUpdates(forSignerId signerId: String,
                                 password passwordText: String,
                                 filterContractIds: [String]? = nil) {
                                 
    self.ps.loadGroup(groupKey: self.groupKey) { (groupData, error) in
        guard let groupData = groupData, let contractsData = groupData.contractData else { return }
        self.psGroupData = groupData
        var titlesOfContracts = [String]()    
        var updatedContractsMessage: String = "We've updated the following: "
        if let cidsFilter = filterContractIds {
            contractsData.forEach { (key, value) in
                if cidsFilter.contains(key) { titlesOfContracts.append(value.title) }
            }
        } else {
            contractsData.forEach { (key, value) in
                titlesOfContracts.append(value.title)
            }
        }
        let contractTitles = titlesOfContracts.map { String($0) }.joined(separator: " and ")
        updatedContractsMessage.append(contractTitles)
        updatedContractsMessage.append(".\n \n Please agree to these changes.")
                                              
        let alert = self.updatedTermsAlert("Updated Terms", message: updatedContractsMessage, email: signerId, password: passwordText)
        self.present(alert, animated: true, completion: nil)
        }
    }
}

By getting these details and using a UIAlertController, you could show an alert for the user that there have been updated terms and provide them a few available actions.

Example UIAlert

Sending Activity Manually

There may be times when you need to send an activity event manually. Doing so just requires using the sendActivity method on your PactSafe shared instance.

Here's an example method that would allow you to send acceptance:

func send(for signer: PSSigner) {
    PSApp.shared.sendActivity(.agreed, signer: signer, group: groupData) { (error) in
        if error !== nil {
            print("Error sending acceptance.")
        }
    }
}

Customizing Acceptance Data

By default, when you send an activity event with the SDK, some additional information about the device will be sent to PactSafe.

There are two parts of data that will be sent as part of the activity event, which you may want to reference as you are implementing the SDK.

  • Connection Data
  • Custom Data

Connection Data

Below, you'll find information on what to expect the SDK to send over as part of the activity event as "Connection Data", which is viewable within a PactSafe activity record. Many of the properties are set upon initialization except the optional properties (marked optional below) and use the following Apple APIs: UIDevice, Locale, and TimeZone. If you need further information about these properties, please reach out to us directly.

PropertyDescriptionOverridable
clientLibraryThe client library name being used that is sent as part of the activity.No
clientVersionThe client library version being used that is sent as part of the activity.No
deviceFingerprintThe unique identifier that is unique and usable to this device.No
environmentThe mobile device category being used (e.g,. tablet or mobile).No
operatingSystemThe operating system and version of the device.No
screenResolutionThe screen resolution of the device.No
browserLocaleThe current locale identifier of the device.Yes
browserTimezoneThe current time zone identifier of the device.Yes
pageDomain (Optional)The domain of the page being viewed. Note: This is normally for web pages but is available to be populated if needed.Yes
pagePath (Optional)The path of the page being viewed. Note: This is normally for web pages but is available to be populated if needed.Yes
pageQuery (Optional)The query path on the page being viewed. Note: This is normally for web pages but is available to be populated if needed.Yes
pageTitle (Optional)The title of the page being viewed. Note: This is normally for web pages but is available to be populated if you'd like to use the title of the screen where the PactSafe activity is occurring.Yes
pageUrl (Optional)The URL of the page being viewed. Note: This is normally for web pages but is available to be populated if needed.Yes
referrer (Optional)The referred of the page being viewed. Note: This is normally for web pages but is avaialble to be populated if needed.Yes

Custom Data

Custom Data can hold additional information that you'd like to pass over that will be appended to the activity event. By adding Custom Data to the event, you'll be able to search and filter within the PactSafe web app, which is especially beneficial when you have many activity events.

Before sending an activity event, you may want to customize properties on PSCustomData that can be set. Please note that properties such as firstName, lastName, companyName, and title that are reserved properties on PSCustomData for PactSafe platform usage only (e.g., seeing the name of an individual within the PactSafe app) but can be set by you.

PropertyDescriptionOverridable
firstNameFirst Name is a reserved property for custom data in PactSafe but can be set.Yes
lastNameLast Name is a reserved property for custom data in PactSafe but can be set.Yes
companyNameCompany Name is a reserved property for custom data in PactSafe but can be set.Yes
titleTitle is a reserved property for custom data in PactSafe but can be set.Yes

Adding Additional Custom Data

When you need to add your own custom data properties and values, you can easily do so by utilizing the add(withKey key: String, value: Any) method on PSCustomData, which accepts a key as a String and value of Any.

Example adding your own custom data:

var customData = PSCustomData()
customData.firstName = firstNameText
customData.lastName = lastNameText
customData.add(withKey: "myCustomKey", value: "myCustomValue")
customData.add(withKey: "mySecondCustomKey", value: "mySecondCustomValue")

You can also remove a previously add key and value by using the method remove(forKey key: String), which removes the value based on the key you previously used.