Latest 0.7.3
Homepage https://github.com/commercetools/commercetools-ios-sdk
License Apache License, Version 2.0
Platforms ios 10.0, osx 10.10, tvos 9.0, watchos 2.2
Authors

Commercetools Swift SDK






Swift 4.0


Platforms iOS | macOS | watchOS | tvOS | Linux


SPM compatible


Installation

Requirements

  • iOS 10.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 9.0+
  • Swift 4.0+

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ gem install cocoapods

CocoaPods 1.2.1+ is required to build CommercetoolsSDK 0.7+.

To integrate CommercetoolsSDK into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

pod 'Commercetools', '~> 0.7'

Then, run the following command:

$ pod install

Getting Started

The Commercetools SDK uses a .plist configuration file named CommercetoolsConfig.plist to gather all information needed to communicate with the commercetools platform.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>projectKey</key>
    <string>Your Project Key</string>
    <key>clientId</key>
    <string>Your Client ID</string>
    <key>clientSecret</key>
    <string>Your Client Secret</string>
    <key>scope</key>
    <string>Your Client Scope</string>
    <key>authUrl</key>
    <string>https://auth.sphere.io/</string>
    <key>apiUrl</key>
    <string>https://api.sphere.io</string>
    <key>anonymousSession</key>
    <true/>
    <key>keychainAccessGroupName</key>
    <string>AB123CDEF.yourKeychainGroup</string>
    <key>shareWatchSession</key>
    <true/>
    <key>emergencyContactInfo</key>
    <string>[email protected]</string>
</dict>
</plist> 

Alternatively, you can specify a path to different .plist file containing these properties.

Before using any methods from the Commercetools SDK, please make sure you have previously set the desired configuration.

import Commercetools

// Default configuration initializer reads from CommercetoolsConfig.plist file from your app bundle
if let configuration = Config() {

    // You can also specify custom logging level
    // configuration.logLevel = .error

    // Or completely disable all log messages from Commercetools SDK
    // configuration.loggingEnabled = false

    // Finally, you need set your configuration before using the SDK
    Commercetools.config = configuration

} else {
    // There are some errors in your .plist file, check log messages for more information
}

Authenticated and Anonymous Usage

Endpoints from the Commercetools services can be consumed without Checkout (PlainToken), with Guest Checkout (AnonymousToken) or with a logged in customer (CustomerToken). According to the configuration, all interactions with the Commercetools platform will be performed with a PlainToken or a AnonymousToken per default.

If at some point you wish to login the user, that can be achieved using loginUser method:

let username = "[email protected]"
let password = "password"

Commercetools.loginCustomer(username, password: password, completionHandler: { result in
    if let error = result.errors?.first as? CTError, case .accessTokenRetrievalFailed(let reason) = error {
        // Handle error, and possibly get some more information from reason.message
    }
})

Similarly, after logging out, all further interactions continue to use new anonymous user token.

Commercetools.logoutCustomer()

Access and refresh tokens are being preserved across app launches. In order to inspect whether it’s currently handling authenticated or anonymous user, authState property should be used:

if Commercetools.authState == .plainToken {
    // Present login form or other logic
}

In order for your app to support anonymous session, you should set the anonymousSession bool property in your configuration .plist file to true. Additionally, it is possible to override this setting, and also provide optional custom anonymous_id (for metrics and tracking purposes) by invoking:

Commercetools.obtainAnonymousToken(usingSession: true, anonymousId: "some-custom-id", completionHandler: { error in
    if error == nil {
        // It is possible for token retrieval to fail, e.g custom token ID has already been taken,
        // in which case reason.message from the returned CTError instance is set to the anonymousId is already in use.
    }
})

When an anonymous sessions ends with a sign up or a login, carts and orders are migrated to the customer, and CustomerSignInResult is returned, providing access to both customer profile, and the currently active cart. For the login operation, you can define how to migrate line items from the currently active cart, by explicitly specifying one of two AnonymousCartSignInMode values: .mergeWithExistingCustomerCart or .useAsNewActiveCustomerCart.

Using the SDK in App Extensions

If your app has extensions, and you want to use Commercetools SDK in those extensions, we recommend enabling keychain sharing. By allowing keychain sharing, and setting the appropriate access group name in the configuration .plist, the SDK will save all tokens in the shared keychain. Be sure to include App ID Prefix / Team ID in the access group name.
As a result, you can use all endpoints with the same authorization state and tokens in both your app and any extension. The same goes for multiple apps from your development team using keychain sharing.

Using the SDK on watchOS

Since the keychain on Apple Watch contains a distinct set of entries from the keychain on the paired iPhone, sharing the same customer session between iOS and watchOS is not possible by setting the keychainAccessGroupName in the configuration .plist. Instead, the Commercetools SDK uses WatchConnectivity to transfer access tokens from an iPhone to an Apple Watch, where they are also stored securely in the watchOS keychain. The only step you have to take to opt in, is to set the shareWatchSession configuration property to true.

A common way for users to log in on Apple Watch is via the iPhone app. The watchOS SDK will post a notification when the access tokens have been received from the iOS app, so you can check the new authState and perform UI changes accordingly.

NotificationCenter.default.addObserver(self, selector: #selector(checkAuthState), name: Notification.Name.WatchSynchronization.DidReceiveTokens, object: nil)

func checkAuthState() {
    if Commercetools.authState == .customerToken {
        // The customer is logged in, present the appropriate screen
    } else {
        // The customer is not logged in, present the login message if needed 
    }
}

Consuming Commercetools Endpoints

Consuming and managing resources provided through available endpoints is very easy for any of the available endpoint classes.

Depending on the capabilities of the resource, you can retrieve by specific UUID, use more detailed query options, and also perform create or update operations.

All of these functionalities are provided by static methods for any specific supported endpoint. For an example, you can creating shopping cart using provided Cart class:

var cartDraft = CartDraft()
cartDraft.currency = "EUR"

Cart.create(cartDraft, result: { result in
    if let cart = result.model, result.isSuccess {
        // Do any work with created `Cart` instance, i.e:
        if cart.cartState == .active {
            // Our cart is active!
        }
    }
})

In case you need resources from an endpoint which hasn’t been implemented in our SDK yet, you can easily create class representing that endpoint, and conform to appropriate protocols which take care of abstract endpoint implementations for many common use cases.

The following list represents currently supported abstract endpoints. For each protocol, there is a default extension provided, which will almost always cover your needs:

  • Create endpoint – create(object: [String: AnyObject], expansion: [String]?, result: (Result<ResponseType>) -> Void)
  • Update endpoint – update(id: String, version: UInt, actions: [[String: AnyObject]], expansion: [String]?, result: (Result<ResponseType>) -> Void)
  • Update by key endpoint – updateByKey(key: String, version: UInt, actions: [[String: AnyObject]], expansion: [String]?, result: (Result<ResponseType>) -> Void)
  • Query endpoint – query(predicates predicates: [String]?, sort: [String]?, expansion: [String]?, limit: UInt?, offset: UInt?, result: (Result<QueryResponse<ResponseType>>) -> Void)
  • Retrieve resource by ID endpoint – byId(id: String, expansion: [String]?, result: (Result<ResponseType>) -> Void)
  • Retrieve resource by key endpoint – byKey(key: String, expansion: [String]?, result: (Result<ResponseType>) -> Void)
  • Delete endpoint – delete(id: String, version: UInt, expansion: [String]?, result: (Result<ResponseType>) -> Void)

Currently Supported Endpoints

Project settings

In order to get the countries, languages, and currencies supported for the current Commercetools project, you should use the project settings endpoint:

  • Retrieve active cart (user must be logged in)
    Project.settings { result in
    if let settings = result.model {
        // use settings.currencies, settings.countries, settings.languages, etc
    }
    }

Cart

Cart endpoint supports all common operations:

  • Retrieve active cart (user must be logged in)
    Cart.active(result: { result in
    if let cart = result.model, result.isSuccess {
        // Cart successfully retrieved, response contains currently active cart
    } else {
        // Your user might not have an active cart at the moment
    }
    })
  • Query for carts (user must be logged in)
    Cart.query(limit: 2, offset: 1, result: { result in
    if let response = result.model, let count = response.count,
            let results = response.results, result.isSuccess {
        // response contains an array of cart instances
    }
    })
  • Create new cart (user must be logged in)
    
    var cartDraft = CartDraft()
    cartDraft.currency = "EUR"

Cart.create(cartDraft, result: { result in
if let cart = result.model, let cartState = cart.cartState, result.isSuccess {
// Cart successfully created, and cart contains
}
})

- Update existing cart (user must be logged in)
```swift
var options = AddLineItemOptions()
options.productId = productId
options.variantId = 1 // Set the appropriate current version

let updateActions = UpdateActions<CartUpdateAction>(version: version, actions: [.addLineItem(options: options)])
Cart.update(cartId, actions: updateActions, result: { result in
    if let cart = result.model, result.isSuccess {
        // Cart successfully updated, response contains updated cart
    }
})
  • Delete existing cart (user must be logged in)
    
    let version = 1 // Set the appropriate current version

Cart.delete(cartId, version: version, result: { result in
if let cart = result.model, result.isSuccess {
// Cart successfully deleted
}
})

- Retrieve cart by UUID (user must be logged in)
```swift
Cart.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
    if let cart = result.model, cart.cartState == .active && result.isSuccess {
        // response contains cart dictionary
    }
})

Category

Using regular mobile scope, it is possible to retrieve by UUID and query for categories.

  • Query for categories
    Category.query(limit: 10, offset: 1, result: { result in
    if let response = result.model, let count = response.count,
            let categories = response.results, result.isSuccess {
        // categories contains an array of category objects
    }
    })
  • Retrieve category by UUID
    Category.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
    if let category = result.model, result.isSuccess {
        // response contains category objects
    }
    })

Customer

Customer endpoint offers you several possible actions to use from your iOS app:

  • Retrieve user profile (user must be logged in)
    Customer.profile { result in
    if let profile = result.model, let firstName = profile.firstName,
            let lastName = profile.lastName, result.isSuccess {
        // E.g present user profile details
    }
    }
  • Sign up for a new account (anonymous user is being handled by AuthManager)
    
    var customerDraft = CustomerDraft()
    customerDraft.email = "[email protected]"
    customerDraft.password = "password"

Customer.signup(customerDraft, result: { result in
if let customer = result.model?.customer, let version = customer.version, result.isSuccess {
// User has been successfully signed up.
// Now, you’d probably want to present the login form, or simply
// use AuthManager to login user automatically
}
})

- Update customer account (user must be logged in)
```swift
var options = SetFirstNameOptions()
options.firstName = "newName"

let updateActions = UpdateActions<CustomerUpdateAction>(version: version, actions: [.setFirstName(options: options)])
Customer.update(actions: updateActions, result: { result in
    if let customer = result.model, let version = customer.version, result.isSuccess {
        // User profile successfully updated
    }
})
  • Delete customer account (user must be logged in)
    Customer.delete(version: version, result: { result in
    if let customer = result.model, result.isSuccess {
        // Customer was successfully deleted
    }
    })
  • Change password (user must be logged in)
    
    let  version = 1 // Set the appropriate current version

Customer.changePassword(currentPassword: "password", newPassword: "newPassword", version: version, result: { result in
if let customer = result.model, result.isSuccess {
// Password has been changed, and now AuthManager has automatically obtained new access token
}
})

- Reset password with token (anonymous user is being handled by `AuthManager`)
```swift
let token = "" // Usually this token is retrieved from the password reset link, in case your app does support universal links

Customer.resetPassword(token: token, newPassword: "password", result: { result in
    if let customer = result.model, let email = customer.email, result.isSuccess {
        // Password has been successfully reset, now would be a good time to present the login screen
    }
})
  • Verify email with token (user must be logged in)
    
    let token = "" // Usually this token is retrieved from the activation link, in case your app does support universal links

Customer.verifyEmail(token: token, result: { result in
if let customer = result.model, let email = customer.email, result.isSuccess {
// Email has been successfully verified, probably show UIAlertController with this info
}
})


#### Order

Order endpoint provides the ability to create an order from an existing `Cart`, but also retrieve orders by UUID, and perform queries for orders.

#### Shipping Method

In order to present shipping options to the customer during checkout, you need to use the shipping method endpoint:
- Retrieve shipping methods for a cart
```swift
ShippingMethod.for(cart: cart) { result in
    if let shippingMethods = result.model, result.isSuccess {
        // present shipping methods to the customer
    }
}
  • Retrieve shipping methods for a country
    ShippingMethod.for(country: "DE") { result in
    if let shippingMethods = result.model, result.isSuccess {
        // present shipping methods to the customer
    }
    }
  • Query for shipping methods
    
    let predicate = "name="DHL""

ShippingMethod.query(predicates: [predicate], result: { result in
if let response = result.model, let count = response.count,
let results = response.results, result.isSuccess {
// results contains an array of shipping method objects
}
})

- Retrieve shipping method by UUID
```swift
ShippingMethod.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
    if let shippingMethod = result.model, result.isSuccess {
        // response contains product projection object
    }
})

Product Projection

Most common way for your iOS app to retrieve the product data is by consuming the product projection endpoint. The following actions are currently supported:

  • Search for product projections
    ProductProjection.search(sort: ["name.en asc"], limit: 10, lang: Locale(identifier: "en"), text: "Michael Kors", result: { result in
    if let response = result.model, let total = response.total,
            let results = response.results, result.isSuccess {
        // results contains an array of product projection objects
    }
    })
  • Product projection keyword suggestions
    ProductProjection.suggest(lang: Locale(identifier: "en"), searchKeywords: "michael", result: { result in
    if let response = result.json, let keywords = response["searchKeywords.en"] as? [[String: AnyObject]],
            let firstKeyword = keywords.first?["text"] as? String, result.isSuccess {
        // keywords contains an array of suggestions in dictionary representation
    }
    })
  • Query for product projections
    
    let predicate = "slug(en="michael-kors-bag-30T3GTVT7L-lightbrown")"

ProductProjection.query(predicates: [predicate], sort: ["name.en asc"], limit: 10, offset: 10, result: { result in
if let response = result.model, let count = response.count,
let results = response.results, result.isSuccess {
// results contains an array of product projection objects
}
})

- Retrieve product projection by UUID
```swift
ProductProjection.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
    if let product = result.model, result.isSuccess {
        // response contains product projection object
    }
})

Product Type

Using regular mobile scope, it is possible to retrieve by UUID, key and query for product types.

  • Query for product types
    ProductType.query(limit: 10, offset: 1, result: { result in
    if let response = result.model, let count = response.count,
            let productTypes = response.results, result.isSuccess {
        // productTypes contains an array of product type objects
    }
    })
  • Retrieve product type by UUID
    ProductType.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
    if let productType = result.model, result.isSuccess {
        // response contains product type object
    }
    })
  • Retrieve product type by key
    ProductType.byKey("main", result: { result in
    if let productType = result.model, result.isSuccess {
        // response contains product type object
    }
    })

Handling Results

In order to check whether any action with Commercetools services was successfully executed, you should use isSuccess or isFailure property of the result in question. For all successful operations, there’re two properties, which can be used to consume actual responses. Recommended one for all endpoints which have incorporated models is model. This property has been used in all of the examples above. Alternatively, in case you are writing a custom endpoint, and do not wish to add model properties and mappings, json property will give you access to [String: Any] (dictionary representation of the JSON received from the Commercetools platform).

For all failed operations, errors property should be used from the result in question to present or handle specific issues. CTError instances are enumerations, with seven main cases, where each of those cases contains FailureReason, and some additional associated values, depending on the specific case.

Tests Setup

If there is a need to implement a custom endpoint which communicates with Commercetools services, it is recommended that such endpoint is also tested. Our XCTestCase extension provides good examples on how to setup test configuration. For some tests regular mobile client scope is sufficient (in most cases view_products manage_my_profile manage_my_orders). If your tests require setup or configuration with higher level privileges (scope), you can setup them as well. SDK tests consume this configuration from the environment variables for safety reasons.

Setting up helper endpoints in test classes is also very easy. You can declare a private class conforming to specific endpoint protocols:

private class Foobar: QueryEndpoint, ByIdEndpoint, CreateEndpoint, UpdateEndpoint, DeleteEndpoint {
    static let path = "foobar"
}

Latest podspec

{
    "name": "Commercetools",
    "version": "0.7.3",
    "summary": "The e-commerce Swift SDK from commercetools",
    "homepage": "https://github.com/commercetools/commercetools-ios-sdk",
    "license": {
        "type": "Apache License, Version 2.0",
        "file": "LICENSE"
    },
    "authors": {
        "Commercetools GmbH": "[email protected]"
    },
    "source": {
        "git": "https://github.com/commercetools/commercetools-ios-sdk.git",
        "tag": "0.7.3"
    },
    "source_files": "Source/*.swift",
    "platforms": {
        "ios": "10.0",
        "osx": "10.10",
        "tvos": "9.0",
        "watchos": "2.2"
    },
    "pushed_with_swift_version": "4.0"
}

Pin It on Pinterest

Share This