NeoDay iOS SDK

The NeoDay SDK provides a convenient way of creating the screens for components like campaigns and coupons that are set up in your NeoDay backoffice.

Setting up NeoDaySDK xcframework file

Minimum iOS version: 12.0

Inside your Xcode project select the App target and go to the General tab. Within the Frameworks, Libraries, and Embedded Content section either drag the NeoDaySDK.xcframework file or tap the + button in order to import the NeoDaySDK.xcframework
On the Build Phases tab make sure the NeoDaySDK.xcframework is added to the sections "Link Binary With Libraries" and "Embed Frameworks".

Add the "import NeoDaySDK" in the files where you want to use the NeoDay SDK.

Implementing the NeoDay SDK

sdk version: 1.2.0

import NeoDay

func initializeSDK() {
    let config = NeoDaySDKConfig(
        preferredDateFormat: "dd-MM-YYYY",
        gatewayConfig: GatewayConfig(
            gateway: URL(string: "https://gateway.url")!,
            clientWebURL: URL(string: "https://web.client.url")!,
            xSiteToken: "x-site-token",
            clientID: "client-id",
            siteStyleID: "site-style-id",
            userSettingsID: "user-settings-id"
        ),
        campaignConfig: CampaignConfig(
            placeholderImage: UIImage(named: "campaign-placeholder")!,
            transactionsHeaderImage: UIImage(named: "header")!,
            buttons: .init(
                backButton: nil,
                closeButton: nil,
                deleteButton: nil
            ),
            scannerConfig: .init(
                scanTitle: "scanTitle",
                scanDescription: "scanDescription",
                manualTitle: "manualTitle",
                manualDescription: "manualDescription"
            )
        ),
        couponConfig: CouponConfig(
            placeholderImage: UIImage(named: "coupon-placeholder")!,
            noDataImage: nil
        )
    )
    
    sdk = NeoDaySDK(config: config)
    
    sdk.deepLinkDelegate = self
    
    sdk.dataLoadingDelegate = self
    
    sdk.needToAuthenticateDelegate = self
    
    let customerAuthenticationEndpoint = CustomerAuthenticationEndpoint(
        url: URL(string: "https://example.url")!,
        headers: nil,
        parameters: nil
    )
    
    sdk.initialize(with: customerAuthenticationEndpoint) { [weak self] result in
        guard let self = self else {
            return
        }
        
        switch result {
            case .success:
                // SDK is initialized

            case let .failure(error):
               // SDK is not initialized, handle the error
        }
    }
}

sdk version: 1.3.0

import NeoDay

func initializeSDK() {
    let config = NeoDaySDKConfig(
        preferredDateFormat: "dd-MM-YYYY",
        gatewayConfig: GatewayConfig(
            gateway: URL(string: "https://gateway.url")!,
            clientWebURL: URL(string: "https://web.client.url")!,
            xSiteToken: "x-site-token",
            clientID: "client-id",
            siteStyleID: "site-style-id"
        ),
        campaignConfig: CampaignConfig(
            placeholderImage: UIImage(named: "campaign-placeholder")!,
            transactionsHeaderImage: UIImage(named: "header")!,
            buttonFactory: { buttonType, siteStyle in },
            scannerConfig: .init(
                scanTitle: "scanTitle",
                scanDescription: "scanDescription",
                manualTitle: "manualTitle",
                manualDescription: "manualDescription"
            )
        ),
        couponConfig: CouponConfig(
            placeholderImage: UIImage(named: "coupon-placeholder")!,
            noDataImage: nil
        ),
        loyaltyCardConfig: .init(
            avatarPlaceholder: UIImage(named: "loyalty-avatar-placeholder")!
        )
    )
    
    sdk = NeoDaySDK(config: config)
    
    sdk.deepLinkDelegate = self
    
    sdk.dataLoadingDelegate = self
    
    sdk.needToAuthenticateDelegate = self
    
    sdk.analyticsTrackingDelegate = self
    
    let customerAuthenticationEndpoint = CustomerAuthenticationEndpoint(
        url: URL(string: "https://example.url")!,
        headers: nil,
        parameters: nil
    )
    
    sdk.authenticateUser(endPoint: customerAuthenticationEndpoint) { [weak self] result in
        guard let self = self else {
            return
        }
        
        switch result {
            case .success:
                // SDK is initialized

            case let .failure(error):
               // SDK is not initialized, handle the error
        }
    }
}

::

sdk version: 1.3.1

import NeoDay

func initializeSDK() {
    let config = NeoDaySDKConfig(
        preferredLocale: NeoDayLocale(languageCode: "en", regionCode: "US"),
        preferredDateFormat: "dd-MM-YYYY",
        gatewayConfig: GatewayConfig(
            gateway: URL(string: "https://gateway.url")!,
            clientWebURL: URL(string: "https://web.client.url")!,
            xSiteToken: "x-site-token",
            clientID: "client-id",
            siteStyleID: "site-style-id"
        ),
        campaignConfig: CampaignConfig(
            placeholderImage: UIImage(named: "campaign-placeholder")!,
            transactionsHeaderImage: UIImage(named: "header")!,
            buttonFactory: { buttonType, siteStyle in },
            scannerConfig: .init(
                scanTitle: "scanTitle",
                scanDescription: "scanDescription",
                manualTitle: "manualTitle",
                manualDescription: "manualDescription"
            )
        ),
        couponConfig: CouponConfig(
            placeholderImage: UIImage(named: "coupon-placeholder")!,
            noDataImage: nil
        ),
        loyaltyCardConfig: .init(
            avatarPlaceholder: UIImage(named: "loyalty-avatar-placeholder")!
        )
    )
    
    // ...
}

Requesting a Screen from the NeoDaySDK

The NeoDaySDK factory methods are executed asynchronously in order to fetch the data and return a UIViewController.

sdk.makeCampaignsOverview(filter: .showOnly([.challenge, .gamification])) { [weak self] result in
    guard let self = self else {
        return
    }
    
    switch result {
        case let .success(viewController):
            // Display the viewController
            
        case let .failure(error):
            // Handle the error
    }
}
    
sdk.makeCouponsOverview { [weak self] result in
    guard let self = self else {
        return
    }
    
    switch result {
        case let .success(viewController):
            // Display the viewController
            
        case let .failure(error):
            // Handle the error
    }
}
    
sdk.makeRewardShop { [weak self] result in
    guard let self = self else {
        return
    }
    
    switch result {
        case let .success(viewController):
            // Display the viewController
            
        case let .failure(error):
            // Handle the error
    }
}

Directly requesting a Campaign detail screen

sdk.makeCampaignDetail(campaignID: "campaign-id", campaignInstanceID: "campaign-instance-id") { [weak self] result in
    guard let self = self else {
        return
    }
    
    switch result {
        case let .success(viewController):
            // Display the viewController
            
        case let .failure(error):
            // Handle the error
    }
}

Directly requesting a Coupon detail screen

sdk.makeCouponDetail(couponSlipID: "coupon-slip-id") { [weak self] result in
    guard let self = self else {
        return
    }
    
    switch result {
        case let .success(viewController):
            // Display the viewController
            
        case let .failure(error):
            // Handle the error
    }
}

Sometimes the SDK needs to display a screen that is part of a user flow, for example when a user taps one of the campaigns on the campaign overview screen the SDK will invoke the NeoDayDeepLinkDelegate that returns the campaign detail screen that needs to be shown.

It is important to set this delegate in order to go from an Overview screen in to a Detail screen.

import NeoDay

class ExampleViewController: UIViewController {

    func deepLinkExample() {
        ...
        // Assuming the sdk is already created.
        
        sdk.deepLinkDelegate = self
        ...
    }
}

extension ExampleViewController: NeoDayDeepLinkDelegate {

    func handleDeepLink(_ deepLink: DeepLink, factoryMethod: (@escaping (Result<UIViewController, Error>) -> Void) -> Void?) {
        
        // You can try to first determine based on the deepLink.identifier if you want to try to make the viewController.
        switch deepLink.identifier {
            case .campaignDetail:
                factoryMethod { result in
                    guard let viewController = try? result.get() else {
                        return
                    }
                    
                    // Display the viewController
                }
                
            case .gamificationCampaign:
                factoryMethod { result in
                    guard let viewController = try? result.get() else {
                        return
                    }
                    
                    // Display the viewController
                }
                
            case .couponDetail:
                factoryMethod { result in
                    guard let viewController = try? result.get() else {
                        return
                    }
                    
                    // Display the viewController
                }
                
            default:
                break
        }
    }
}

How to handle re-authentication when the AuthenticationToken can no longer be refreshed

When the SDK can no longer refresh the token it will invoke the NeedToAuthenticateDelegate so your app can reinitialize the SDK.

It's important to set this delegate when using the NeoDay SDK.

import NeoDay

class ExampleViewController: UIViewController {

    func authenticationExample() {
        ...
        // Assuming the sdk is already created.
        
        sdk.needToAuthenticateDelegate = self
        ...
    }
}

sdk version: 1.2.0

extension ExampleViewController: NeedToAuthenticateDelegate {

    func authenticationRequired() {     
        ...
        // Assuming the customerAuthenticationEndpoint is already defined

        sdk.initialize(with: customerAuthenticationEndpoint) { [weak self] result in
            guard let self = self else {
                return
            }
            
            switch result {
                case .success:
                    // SDK is initialized
    
                case let .failure(error):
                   // SDK is not initialized, handle the error
            }
        }
    }
}

sdk version: 1.4.2

After the AuthenticationToken has been refreshed by the NeoDay SDK, the new token is returned by this function. It can be stored and used with the authenticateUser(token: completion:) if needed. When using the authenticateUser(endPoint: completion) to authenticate with NeoDay, it's advised to use the authenticationRequired function of the NeedToAuthenticateDelegate

extension ExampleViewController: NeedToAuthenticateDelegate {
    
    func authenticationRefreshed(token: AuthenticationToken) {
        ...
                
    }
}

How to handle when the SDK is processing data

When the SDK is fetching from or submitting data to the NeodDay Gateway, it will invoke the SDKDataLoadingDelegate to notify when data is being loaded or not. That way you know when to show or hide a loading indicator while data is being processed.

It's important to set this delegate when using the NeoDay SDK.

sdk version: 1.2.0, 1.3.0

import NeoDay

class ExampleViewController: UIViewController {

    func dataLoadingExample() {
        ...
        // Assuming the sdk is already created.
        
        sdk.dataLoadingDelegate = self
        ...
    }
}

extension ExampleViewController: SDKDataLoadingDelegate {

    func startedLoadingData() {
        // Show a loading indicator
    }
    
    func finishedLoadingData() {
        // Hide the loading indicator
    }
}

sdk version: 1.2.0

/// Configures the NeoDay SDK in order to communicate with the Backoffice
///
/// - parameter gateway: The URL used to communicate with the backend
/// - parameter clientWebURL: The URL of the NeoDay web client
/// - parameter xSiteToken: Used for identification
/// - parameter clientID: Used to identify the client
/// - parameter: siteStyleID: The id used to get SiteStyle that is configured inside the Backoffice
struct GatewayConfig {
    let gateway: URL
    let clientWebURL: URL
    let xSiteToken: String
    let clientID: String
    let siteStyleID: String
}

struct CampaignConfig {
   let placeholderImage: UIImage
   let transactionsHeaderImage: UIImage
   let noDataImage: UIImage?
   let buttons: CampaignButtons
   let scannerConfig: ScannerConfig
}

struct CampaignButtons {
   let backButton: UIButton?
   let closeButton: UIButton?
   let deleteButton: UIButton?
}

struct ScannerConfig {
   let scanTitle: String
   let scanDescription: String
   let manualTitle: String
   let manualDescription: String
}

 struct CouponConfig {
   let placeholderImage: UIImage
   let noDataImage: UIImage?
}

struct NeoDaySDKConfig {
   let preferredDateFormat: String
   let gatewayConfig: GatewayConfig
   let campaignConfig: CampaignConfig
   let couponConfig: CouponConfig?
}

struct AuthenticationToken {
    let accessToken: String
    let accessTokenExpirationDate: Date
    let refreshToken: String
}

struct CustomerAuthenticationEndpoint {
    let baseURL: URL
    let headers: [String: String]?
    let parameters: [String: Any]?
}

class NeoDaySDK {

    private(set) public var siteStyle: SiteStyle?
    
    // When set, it means the client will be notified when data is being loaded and a loading indicator should be shown or not in the process
    public weak var dataLoadingDelegate: SDKDataLoadingDelegate? {
    
     // When set, it means the client will be responsible for showing the Feedback
    public weak var feedbackDelegate: FeedbackDelegate?
    
    // When set, it means the client will be responsible for showing the Survey
    public weak var surveyDelegate: SurveyDelegate?
    
    // When set, it means the client will be alerted when authentication is required again
    public weak var needToAuthenticateDelegate: NeedToAuthenticateDelegate?

     /// Configures the NeoDay SDK.
    ///
    /// - parameter config: A configuration object for configuration the NeoDay SDK.
    init(config: NeoDaySDKConfig)
    
    /// Initializes the NeoDay SDK by using a signed token for the bearing user.
    ///
    /// - parameter endPoint: The endpoint that will return the signed token for the bearing user.
    /// - parameter completion: A success or an Error.
    public func initialize(
        with endPoint: CustomerAuthenticationEndpoint,
        _ completion: @escaping (Result<Void, Error>) -> Void
    )  
    
    /// Initializes the NeoDay SDK by using an AuthenticationToken.
    ///
    /// - parameter token: The AuthenticationToken that will be used for the user.
    /// - parameter completion: The result of using the AuthenticationToken.
    public func initialize(
        with token: AuthenticationToken,
        _ completion: @escaping (Result<Void, Error>) -> Void
    )
    
    /// Retrieves the AuthenticationToken that is used by the NeoDay SDK.
    ///
    /// - returns: Optionally the AuthenticationToken that the NeoDay SDK uses.
    public func retrieveStoredToken() throws -> AuthenticationToken?
    
    /// Removes the AuthenticationToken stored by the NeoDay SDK.
    ///
    public func removeToken()

sdk version: 1.3.0

/// Configures the NeoDay SDK in order to communicate with the Backoffice
///
/// - parameter gateway: The URL used to communicate with the backend
/// - parameter clientWebURL: The URL of the NeoDay web client
/// - parameter xSiteToken: Used for identification
/// - parameter clientID: Used to identify the client
/// - parameter: siteStyleID: The id used to get SiteStyle that is configured inside the Backoffice
struct GatewayConfig {
    let gateway: URL
    let clientWebURL: URL
    let xSiteToken: String
    let clientID: String
    let siteStyleID: String
}

struct CampaignConfig {
   let placeholderImage: UIImage
   let transactionsHeaderImage: UIImage
   let noDataImage: UIImage?
   let buttonFactory: (NDButtonType, SiteStyle) -> UIButton?
   let scannerConfig: ScannerConfig
}

enum NDButtonType {
    case back
    case close
    case delete
}

struct ScannerConfig {
   let scanTitle: String
   let scanDescription: String
   let manualTitle: String
   let manualDescription: String
}

 struct CouponConfig {
   let placeholderImage: UIImage
   let noDataImage: UIImage?
}

struct NeoDaySDKConfig {
   let preferredLocale: NeoDayLocale
   let preferredDateFormat: String
   let webViewTimeoutDuration: TimeInterval
   let gatewayConfig: GatewayConfig
   let campaignConfig: CampaignConfig
   let couponConfig: CouponConfig?
   let loyaltyCardConfig: LoyaltyCardConfig?
}

struct AuthenticationToken {
    let accessToken: String
    let accessTokenExpirationDate: Date
    let refreshToken: String
}

struct CustomerAuthenticationEndpoint {
    let baseURL: URL
    let headers: [String: String]?
    let parameters: [String: Any]?
}

class NeoDaySDK {

    private(set) public var siteStyle: SiteStyle?
    
    // When set, it means the client will be notified when data is being loaded and a loading indicator should be shown or not in the process
    public weak var dataLoadingDelegate: SDKDataLoadingDelegate? {
    
     // When set, it means the client will be responsible for showing the Feedback
    public weak var feedbackDelegate: FeedbackDelegate?
    
    // When set, it means the client will be responsible for showing the Survey
    public weak var surveyDelegate: SurveyDelegate?
    
    // When set, it means the client will be alerted when authentication is required again
    public weak var needToAuthenticateDelegate: NeedToAuthenticateDelegate?
    
    public weak var analyticsTrackingDelegate: NeoDayAnalyticsTrackingDelegate?

     /// Configures the NeoDay SDK.
    ///
    /// - parameter config: A configuration object for configuration the NeoDay SDK.
    init(config: NeoDaySDKConfig)
    
    /// Authenticate with NeoDay by using a signed token for the bearing user.
    ///
    /// - parameter endPoint: The endpoint that will return the signed token for the bearing user.
    /// - parameter completion: The AuthenticationToken that will be used or an Error.
    public func authenticateUser(
        endPoint: CustomerAuthenticationEndpoint,
        completion: @escaping (Result<Void, Error>) -> Void
    )
    
    /// Authenticate with NeoDay by using an AuthenticationToken.
    ///
    /// - parameter token: The AuthenticationToken that will be used for the user.
    /// - parameter completion: The result of using the AuthenticationToken.
    public func authenticateUser(
        token: AuthenticationToken,
        completion: @escaping (Result<Void, Error>) -> Void
    )
    
    /// Retrieves the AuthenticationToken that is used by the NeoDay SDK.
    ///
    /// - returns: Optionally the AuthenticationToken that the NeoDay SDK uses.
    public func retrieveStoredToken() throws -> AuthenticationToken?
    
    /// Removes the AuthenticationToken stored by the NeoDay SDK.
    ///
    public func removeToken()

sdk version: 1.3.1

The preferredLocale was added to the NeoDaySDKConfig. The default value for this field is the current device's locale.


struct NeoDaySDKConfig {
  let preferredLocale: NeoDayLocale
  let preferredDateFormat: String
  let webViewTimeoutDuration: TimeInterval
  let gatewayConfig: GatewayConfig
  let campaignConfig: CampaignConfig
  let couponConfig: CouponConfig?
  let loyaltyCardConfig: LoyaltyCardConfig?
}

sdk version: 1.5.0

The optional pointsIcon is added to the CampaignConfig which can be seen on the screen created by makeCampaignTransactionsHistory(campaignID: blockID: limit: _ completion:)


struct CampaignConfig {
  let placeholderImage: UIImage
  let transactionsHeaderImage: UIImage
  let noDataImage: UIImage?
  let pointsIcon: UIImage?
  let buttonFactory: (NDButtonType, SiteStyle) -> UIButton?
  let scannerConfig: ScannerConfig   
}

The NeoDaySDKDebugDelegate is added:

class NeoDaySDK {
    ...
    
   public weak var debugDelegate: NeoDaySDKDebugDelegate?
   
    ...
}

It can return the following types of debug messages related to Site Style colors or fonts:

When the Backoffice configured Site Style color, cannot be interpreted:

"SiteStyle color cannot be interpreted, a 'clear' color is used instead."

When the Backoffice configured Site Style font family cannot be interpreted:

"SiteStyle font family cannot be interpreted, default font <font family name> is used instead."

When the Backoffice configured Site Style font for a font tag and weight cannot be interpreted:

"SiteStyle font cannot be interpreted for fontTag: <fontTag> and weight: <font weight>, default font <default font> is used instead."

sdk version: 1.7.0

The Campaign Counter is introduced on screens created by makeCampaignDetail(campaignID: campaignInstanceID: _ completion:)