> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowx.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS SDK

> Install and configure the FlowX.AI iOS SDK to render process-driven UI in native iOS apps. Requires iOS 15+ and Swift 5.7.

# Using the iOS Renderer

## iOS Project Requirements

The minimum requirements are:

* iOS 15
* Swift 5.7

## Installing the library

The iOS Renderer is available through Cocoapods.

### Cocoapods

#### Prerequisites

* Cocoapods gem installed

#### Cocoapods private trunk setup

Add the private trunk repo to your local Cocoapods installation with the command:

```ruby theme={"system"}
pod repo add flowx-specs git@github.com:flowx-ai-external/flowx-ios-specs.git
```

#### Adding the dependency

Add the source of the private repository in the Podfile

```ruby theme={"system"}
source 'git@github.com:flowx-ai-external/flowx-ios-specs.git'
```

Add a post install hook in the Podfile setting `BUILD_LIBRARY_FOR_DISTRIBUTION` to `YES`.

```ruby theme={"system"}
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
    end
  end
end
```

Add the pod and then run `pod install`

```ruby theme={"system"}
pod 'FlowXRenderer'
```

Example

```ruby theme={"system"}
source 'https://github.com/flowx-ai-external/flowx-ios-specs.git'
source 'https://github.com/CocoaPods/Specs.git'

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
    end
  end
end

target 'AppTemplate' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  
  # Pods for AppTemplate
  pod 'FlowXRenderer'
  
  target 'AppTemplateTests' do
    inherit! :search_paths
    # Pods for testing
  end
  
  target 'AppTemplateUITests' do
    # Pods for testing
  end
  
end
```

### Library dependencies

* Alamofire
* SDWebImageSwiftUI
* SDWebImageSVGCoder

## Configuring the library

The SDK has 2 configurations, available through shared instances: `FXConfig` which holds general purpose properties, and `FXSessionConfig` which holds user session properties.

It is recommended to call the `FXConfig` configuration method at app launch.

Call the FXSessionConfig configure method after the user logs in and a valid user session is available.

### FXConfig

This config is used for general purpose properties.

#### Properties

| Name                  | Description                                                                      | Type              | Requirement                    |
| --------------------- | -------------------------------------------------------------------------------- | ----------------- | ------------------------------ |
| baseURL               | The base URL used for REST networking                                            | String            | Mandatory                      |
| enginePath            | The process engine url path component                                            | String            | Mandatory                      |
| imageBaseURL          | The base URL used for static assets                                              | String            | Mandatory                      |
| organizationId        | The UUID of the FlowX organization the host app belongs to                       | String            | Mandatory                      |
| locale                | The locale used for localization                                                 | String            | Mandatory. Defaults to "en-us" |
| language              | The language used for retrieving enumerations and substitution tags              | String            | Mandatory. Defaults to "en"    |
| logLevel              | Enum value indicating the log level logging. Default is none                     | FXLogLevel        | Optional                       |
| additionalHttpHeaders | Additional HTTP headers included in all SDK network requests                     | \[String: String] | Optional. Defaults to \[:]     |
| cacheDocuments        | Whether to cache documents locally                                               | Bool              | Optional. Defaults to true     |
| updateStateEnabled    | Whether to check and update process state when the app returns to the foreground | Bool              | Optional. Defaults to true     |

<Info>
  `organizationId` is the FlowX-issued organization UUID. On self-hosted deployments it matches the `ORGANIZATION_ID` configured during the backend upgrade — see [License and organization configuration](/5.9/migrating-from-5.1-lts/organization-deployment).
</Info>

**Sample**

```swift theme={"system"}
FXConfig.sharedInstance.configure { (config) in
    config.baseURL = myBaseURL
    config.enginePath = "engine"
    config.imageBaseURL = myImageBaseURL
    config.organizationId = myOrganizationUuid
    config.locale = "en-us" 
    config.language = "en" 
    config.logLevel = .verbose
    config.additionalHttpHeaders = ["X-Custom-Header": "value"]
    config.cacheDocuments = true
    config.updateStateEnabled = true
}
```

#### Changing the current language

The current language and locale can be changed after the initial configuration, by calling the `changeLocaleSettings` method:

```swift theme={"system"}
public func changeLocaleSettings(locale: String?,
                                 language: String?)
```

```swift theme={"system"}
FXConfig.sharedInstance.changeLocaleSettings(locale: "ro-ro",
                                             language: "en")
```

<Info>
  For locale, use the format BCP 47. BCP 47 is the standard for representing language and region codes, here is a [link](https://www.w3.org/International/articles/language-tags/) to the BCP 47 specification.
  An example of BCP 47 is `en-us` (language code - `en` and region code - `us`).
</Info>

### FXSessionConfig

This config is used for providing networking or auth session-specific properties.

The library expects either the JWT access token or an Alamofire Session instance managed by the container app. In case a session object is provided, the request adapting should be handled by the container app.

#### Properties

| Name           | Description                                         | Type    |
| -------------- | --------------------------------------------------- | ------- |
| sessionManager | Alamofire session instance used for REST networking | Session |
| token          | JWT authentication access token                     | String  |

#### Sample for access token

```swift theme={"system"}
    ...
    func configureFlowXSession() {
        FXSessionConfig.sharedInstance.configure { config in
            config.token = myAccessToken
        }
    }   

```

#### Sample for session

```swift theme={"system"}
import Alamofire
```

```swift theme={"system"}
    ...
    func configureFlowXSession() {
        FXSessionConfig.sharedInstance.configure { config in
            config.sessionManager = Session(interceptor: MyRequestInterceptor())
        }
    }   

```

```swift theme={"system"}
class MyRequestInterceptor: RequestInterceptor {
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Swift.Result<URLRequest, Error>) -> Void) {
        var urlRequest = urlRequest
        urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
        completion(.success(urlRequest))
    }    
}
```

### Theming

<Warning>Make sure the `FXSessionConfig` configure method was called with a valid session before setting up the theme.</Warning>

Before starting or resuming a process, the theme setup API should be called.
The start or continue process APIs should be called only after the theme setup was completed.

### Theme setup

The setup theme is called using the shared instance of `FXTheme`

```swift theme={"system"}
public func setupTheme(withUuid uuid: String,
                       workspaceId: String,
                       localFileUrl: URL? = nil,
                       appearance: SwiftUI.ColorScheme? = .light,
                       completion: (() -> Void)?)
```

* `uuid` - the UUID of the theme configured in the FlowX Designer.

* `workspaceId` - the UUID of the workspace the theme belongs to.

* `localFileUrl` - optional parameter for providing a fallback theme file, in case the fetch theme request fails.

* `appearance` - optional SwiftUI `ColorScheme` (`.light` or `.dark`) used to render the theme. Defaults to `.light`.

* `completion` - a completion closure called when the theme setup finishes.

In addition to the `completion` parameter, FXTheme's shared instance also provides a Combine publisher named `themeFetched` which sends `true` if the theme setup was finished.

#### Sample

```swift theme={"system"}
FXTheme.sharedInstance.setupTheme(withUuid: myThemeUuid,
                                  workspaceId: workspaceUuid,
                                  localFileUrl: Bundle.main.url(forResource: "theme", withExtension: "json"),
                                  appearance: .light,
                                  completion: {
    print("theme setup finished")
})
```

```swift theme={"system"}
...
    var subscription: AnyCancellable?

    func myMethodForThemeSetupFinished() {
        subscription = FXTheme.sharedInstance.themeFetched.sink { result in
            if result {
                DispatchQueue.main.async {
                    // you can now start/continue a process
                }
            }
        }
    }
}
    
...

```

## Using the library

### Public API

The library's public APIs described in this section are called using the shared instance of FlowX, `FlowX.sharedInstance`.

### How to start and end FlowX session

After all the configurations are set, you can start a FlowX session by calling the `startSession()` method.

This is optional, as the session starts lazily when the first process is started.

`FlowX.sharedInstance.startSession()`

When you want to end a FlowX session, you can call the `endSession()` method. This also does a complete clean-up of the started processes.

You might want to use this method in a variety of scenarios, for instance when the user logs out.

`FlowX.sharedInstance.endSession()`

### How to start a process

There are 3 methods available for starting a FlowX process.
The container app is responsible with presenting the navigation controller or tab controller holding the process navigation.

1. Start a process which renders inside an instance of `UINavigationController` or `UITabBarController`, depending on the BPMN diagram of the process.

The controller to be presented is provided through the `completion` closure parameter of the method.

<Tip>Use this method when you want the process to be rendered inside a controller themed using the FlowX Theme defined in the FlowX Designer, regardless of the navigation type configured for the process.</Tip>

```swift theme={"system"}
public func startProcess(workspaceId: String,
                         projectId: String,
                         name: String,
                         params: [String: Any]?,
                         isModal: Bool = false,
                         showLoader: Bool = false,
                         completion: ((UIViewController?, String) -> Void)?,
                         onProcessEnded: (() -> Void)? = nil)
```

* `workspaceId` - the uuid of the workspace

* `projectId` - the uuid of the project

* `name` - the name of the process

* `params` - the start parameters, if any

* `isModal` - a boolean indicating whether the process navigation is modally displayed. When the process navigation is displayed modally, a close bar button item is displayed on each screen displayed throughout the process navigation.

* `showLoader` - a boolean indicating whether the loader should be displayed when starting the process.

* `completion` - a completion closure which passes either an instance of `UINavigationController` or `UITabBarController` to be presented, alongside the started process instance UUID. The UUID is an empty string if the process failed to start.

* `onProcessEnded` - a closure called when the process ends. The closure is strongly referenced inside the SDK. Avoid reference cycles by using \[weak self]

2. Start a process which renders inside a provided instance of a `UINavigationController`.

<Tip>Use this method when you want the process to be rendered inside a custom instance of `UINavigationController`.<br />Optionally you can pass an instance of `FXNavigationViewController`, which has the appearance set in the FlowX Theme, using the `FXNavigationViewController`s class func `FXNavigationViewController.navigationController()`.</Tip>

If you use this method, make sure that the process does not use a tab controller as root view.

```swift theme={"system"}
public func startProcess(navigationController: UINavigationController,
                         workspaceId: String,
                         projectId: String,
                         name: String,
                         params: [String: Any]?,
                         isModal: Bool = false,
                         showLoader: Bool = false,
                         onProcessStarted: ((String) -> Void)? = nil,
                         onProcessEnded: (() -> Void)? = nil)
```

* `navigationController` - the instance of UINavigationController which will hold the process navigation stack

* `workspaceId` - the uuid of the workspace

* `projectId` - the uuid of the project

* `name` - the name of the process

* `params` - the start parameters, if any

* `isModal` - a boolean indicating whether the process navigation is modally displayed. When the process navigation is displayed modally, a close bar button item is displayed on each screen displayed throughout the process navigation.

* `showLoader` - a boolean indicating whether the loader should be displayed when starting the process.

* `onProcessStarted` - an optional closure called after the process has been started, providing the process instance UUID.

* `onProcessEnded` - a closure called when the process ends. The closure is strongly referenced inside the SDK. Avoid reference cycles by using \[weak self]

3. Start a process which renders inside a provided instance of a `UITabBarController`.

<Tip>Use this method when you want the process to be rendered inside a custom instance of `UITabBarController`.</Tip>

<Warning>If you use this method, make sure that the process has a tab controller as root view.</Warning>

```swift theme={"system"}
public func startProcess(tabBarController: UITabBarController,
                         workspaceId: String,
                         projectId: String,
                         name: String,
                         params: [String: Any]?,
                         isModal: Bool = false,
                         showLoader: Bool = false,
                         onProcessStarted: ((String) -> Void)? = nil,
                         onProcessEnded: (() -> Void)? = nil)
```

* `tabBarController` - the instance of UITabBarController which will hold the process navigation

* `workspaceId` - the uuid of the workspace

* `projectId` - the uuid of the project

* `name` - the name of the process

* `params` - the start parameters, if any

* `isModal` - a boolean indicating whether the process navigation is modally displayed. When the process navigation is displayed modally, a close bar button item is displayed on each screen displayed throughout the process navigation.

* `showLoader` - a boolean indicating whether the loader should be displayed when starting the process.

* `onProcessStarted` - an optional closure called after the process has been started, providing the process instance UUID.

* `onProcessEnded` - a closure called when the process ends. The closure is strongly referenced inside the SDK. Avoid reference cycles by using \[weak self]

#### Sample

```swift theme={"system"}
FlowX.sharedInstance.startProcess(workspaceId: workspaceUuid,
                                  projectId: applicationUuid,
                                  name: processName,
                                  params: [:],
                                  isModal: true,
                                  showLoader: true) { processRootViewController, processInstanceUuid in
    if let processRootViewController = processRootViewController {
        processRootViewController.modalPresentationStyle = .overFullScreen
        self.present(processRootViewController, animated: false)
    }
    //TODO use processInstanceUuid if needed
} onProcessEnded: { [weak self] in
    //TODO handle process end
}
```

or

```swift theme={"system"}
FlowX.sharedInstance.startProcess(navigationController: processNavigationController,
                                  workspaceId: workspaceUuid,
                                  projectId: applicationUuid,
                                  name: processName,
                                  params: [:],
                                  isModal: true,
                                  showLoader: true,
                                  onProcessStarted: { processInstanceUuid in
    //TODO use processInstanceUuid if needed
}, onProcessEnded: { [weak self] in
    //TODO handle process end
})

self.present(processNavigationController, animated: true, completion: nil)
```

or

```swift theme={"system"}
FlowX.sharedInstance.startProcess(tabBarController: processTabController,
                                  workspaceId: workspaceUuid,
                                  projectId: applicationUuid,
                                  name: processName,
                                  params: [:],
                                  isModal: true,
                                  showLoader: true,
                                  onProcessStarted: { processInstanceUuid in
    //TODO use processInstanceUuid if needed
}, onProcessEnded: { [weak self] in
    //TODO handle process end
})

self.present(processTabController, animated: true, completion: nil)
```

### How to start a UI Flow

UI Flows are lightweight, screen-driven flows defined in the FlowX Designer. They run client-side on top of a data model, without requiring a BPMN process instance on the engine.

The container app is responsible with presenting the controller passed in the `completion` closure.

```swift theme={"system"}
public func startUIFlow(workspaceId: String,
                        projectId: String,
                        name: String,
                        params: [String: Any]?,
                        isModal: Bool = false,
                        showLoader: Bool = false,
                        completion: ((UIViewController?) -> Void)?)
```

* `workspaceId` - the uuid of the workspace

* `projectId` - the uuid of the project

* `name` - the name of the UI Flow

* `params` - the start parameters, if any

* `isModal` - a boolean indicating whether the UI Flow navigation is modally displayed. When the navigation is displayed modally, a close bar button item is displayed on each screen.

* `showLoader` - a boolean indicating whether the loader should be displayed when starting the UI Flow.

* `completion` - a completion closure which passes either an instance of `UINavigationController` or `UITabBarController` to be presented, depending on the navigation configured for the UI Flow.

#### Sample

```swift theme={"system"}
FlowX.sharedInstance.startUIFlow(workspaceId: workspaceUuid,
                                 projectId: applicationUuid,
                                 name: uiFlowName,
                                 params: [:],
                                 isModal: true,
                                 showLoader: true) { rootViewController in
    if let rootViewController = rootViewController {
        rootViewController.modalPresentationStyle = .overFullScreen
        self.present(rootViewController, animated: false)
    }
}
```

### How to resume an existing process

There are 3 methods available for resuming a FlowX process.
The container app is responsible with presenting the navigation controller or tab controller holding the process navigation.

1. Continue a process which renders inside an instance of `UINavigationController` or `UITabBarController`, depending on the BPMN diagram of the process.

The controller to be presented will be provided inside the `completion` closure parameter of the method.

<Tip>Use this method when you want the process to be rendered inside a controller themed using the FlowX Theme defined in the FlowX Designer. </Tip>

```swift theme={"system"}
public func continueExistingProcess(uuid: String,
                                    name: String,
                                    isModal: Bool = false,
                                    showLoader: Bool = false,
                                    completion: ((UIViewController?) -> Void)? = nil,
                                    onProcessEnded: (() -> Void)? = nil)
```

* `uuid` - the UUID string of the process

* `name` - the name of the process

* `isModal` - a boolean indicating whether the process navigation is modally displayed. When the process navigation is displayed modally, a close bar button item is displayed on each screen displayed throughout the process navigation.

* `showLoader` - a boolean indicating whether the loader should be displayed when resuming the process.

* `completion` - a completion closure which passes either an instance of `UINavigationController` or `UITabBarController` to be presented.

* `onProcessEnded` - a closure called when the process ends. The closure is strongly referenced inside the SDK. Avoid reference cycles by using \[weak self]

2. Continue a process which renders inside a provided instance of a `UINavigationController`.

<Tip>Use this method when you want the process to be rendered inside a custom instance of `UINavigationController`.<br />Optionally you can pass an instance of `FXNavigationViewController`, which has the appearance set in the FlowX Theme, using the `FXNavigationViewController`s class func `FXNavigationViewController.navigationController()`.</Tip>

If you use this method, make sure that the process does not use a tab controller as root view.

```swift theme={"system"}
public func continueExistingProcess(uuid: String,
                                    name: String,
                                    navigationController: UINavigationController,
                                    isModal: Bool = false,
                                    showLoader: Bool = false,
                                    onProcessEnded: (() -> Void)? = nil)
```

* `uuid` - the UUID string of the process

* `name` - the name of the process

* `navigationController` - the instance of UINavigationController which will hold the process navigation stack

* `isModal` - a boolean indicating whether the process navigation is modally displayed. When the process navigation is displayed modally, a close bar button item is displayed on each screen displayed throughout the process navigation.

* `showLoader` - a boolean indicating whether the loader should be displayed when resuming the process.

* `onProcessEnded` - a closure called when the process ends. The closure is strongly referenced inside the SDK. Avoid reference cycles by using \[weak self]

3. Continue a process which renders inside a provided instance of a `UITabBarController`.

<Tip>Use this method when you want the process to be rendered inside a custom instance of `UITabBarController`.</Tip>

<Warning>If you use this method, make sure that the process has a tab controller as root view.</Warning>

```swift theme={"system"}
public func continueExistingProcess(uuid: String,
                                    name: String,
                                    tabBarController: UITabBarController,
                                    isModal: Bool = false,
                                    showLoader: Bool = false,
                                    onProcessEnded: (() -> Void)? = nil)
```

* `uuid` - the UUID string of the process

* `name` - the name of the process

* `tabBarController` - the instance of UITabBarController which will hold the process navigation

* `isModal` - a boolean indicating whether the process navigation is modally displayed. When the process navigation is displayed modally, a close bar button item is displayed on each screen displayed throughout the process navigation.

* `showLoader` - a boolean indicating whether the loader should be displayed when resuming the process.

* `onProcessEnded` - a closure called when the process ends. The closure is strongly referenced inside the SDK. Avoid reference cycles by using \[weak self]

#### Sample

```swift theme={"system"}
FlowX.sharedInstance.continueExistingProcess(uuid: uuid,
                                             name: processName,
                                             isModal: true) { processRootViewController in
    if let processRootViewController = processRootViewController {
        processRootViewController.modalPresentationStyle = .overFullScreen
        self.present(processRootViewController, animated: true)
    }
} onProcessEnded: { [weak self] in
    
}
```

or

```swift theme={"system"}
FlowX.sharedInstance.continueExistingProcess(uuid: uuid,
                                             name: processName,
                                             navigationController: processNavigationController,
                                             isModal: true)
processNavigationController.modalPresentationStyle = .overFullScreen

self.present(processNavigationController, animated: true, completion: nil)
```

or

```swift theme={"system"}
FlowX.sharedInstance.continueExistingProcess(uuid: uuid,
                                             name: processName,
                                             tabBarController: processTabBarController,
                                             isModal: false)
processTabBarController.modalPresentationStyle = .overFullScreen

self.present(processTabBarController, animated: true, completion: nil)
```

### How to resume the latest process

You can resume the most recently started process using `continueLatestProcess`. This method uses the internally tracked latest process instance UUID.

```swift theme={"system"}
public func continueLatestProcess(name: String,
                                  navigationController: UINavigationController,
                                  isModal: Bool = false)
```

* `name` - the name of the process

* `navigationController` - the instance of UINavigationController which will hold the process navigation stack

* `isModal` - a boolean indicating whether the process navigation is modally displayed

#### Sample

```swift theme={"system"}
FlowX.sharedInstance.continueLatestProcess(name: processName,
                                           navigationController: processNavigationController,
                                           isModal: true)

self.present(processNavigationController, animated: true, completion: nil)
```

### How to end a process

You can manually end a process by calling the `stopProcess(name: String)` method.

This is useful when you want to explicitly ask the FlowX shared instance to clean up the instance of the process sent as parameter.

For example, it could be used for modally displayed processes that are dismissed by the user, in which case the `dismissRequested(forProcess process: String, navigationController: UINavigationController)` method of the FXDataSource will be called.

#### Sample

```swift theme={"system"}
FlowX.sharedInstance.stopProcess(name: processName)
```

### FXDataSource

The library offers a way of communication with the container app through the `FXDataSource` protocol.

The data source is a public property of FlowX shared instance.

`public weak var dataSource: FXDataSource?`

```swift theme={"system"}
public protocol FXDataSource: AnyObject {
    func controllerFor(componentIdentifier: String) -> FXController?
    
    func viewFor(componentIdentifier: String) -> FXView?
    
    func viewFor(componentIdentifier: String, customComponentViewModel: FXCustomComponentViewModel) -> AnyView?

    func navigationController() -> UINavigationController?

    func errorReceivedForAction(name: String?)
    
    func validate(validatorName: String, value: Any, params: [String]?) -> Bool
    
    func dismissRequested(forProcess process: String, navigationController: UINavigationController)
    
    func viewForStepperHeader(stepViewModel: StepViewModel) -> AnyView?

    func collect(event: AnalyticsEvent)
 
    func newProcessStarted(processInstanceUuid: String)
    
    func customLoader(type: CustomLoaderType) -> AnyView?
}
```

* `func controllerFor(componentIdentifier: String) -> FXController?`

This method is used for providing a custom component using UIKit UIViewController, identified by the componentIdentifier argument.

* `func viewFor(componentIdentifier: String) -> FXView?`

This method is used for providing a custom component using UIKit UIView, identified by the componentIdentifier argument.

* `func viewFor(componentIdentifier: String, customComponentViewModel: FXCustomComponentViewModel) -> AnyView?`

This method is used for providing a custom component using SwiftUI View, identified by the componentIdentifier argument.
A view model is provided as an ObservableObject to be added as @ObservedObject inside the SwiftUI view for component data observation.

* `func navigationController() -> UINavigationController?`

This method is used for providing a navigation controller. It can be either a custom `UINavigationController` class, or just a regular `UINavigationController` instance themed by the container app.

* `func errorReceivedForAction(name: String?)`

This method is called when an error occurs after an action is executed.

* `func validate(validatorName: String, value: Any, params: [String]?) -> Bool`

This method is used for custom validators. It provides the name of the validator, the value to be validated, and an optional array of parameter strings. The method returns a boolean indicating whether the value is valid or not.

* `func dismissRequested(forProcess process: String, navigationController: UINavigationController)`

This method is called, on a modally displayed process navigation, when the user attempts to dismiss the modal navigation. Typically it is used when you want to present a confirmation pop-up.

The container app is responsible with dismissing the UI and calling the stop process APIs.

* `func viewForStepperHeader(stepViewModel: StepViewModel) -> AnyView?`

This method is used for providing a custom SwiftUI view for the stepper navigation header.

* `func collect(event: AnalyticsEvent)`

This method is used for collecting analytics events from the SDK. The parameter is a `AnalyticsEvent` enum, which can represent a screen or an action.

* `func newProcessStarted(processInstanceUuid: String)`

This method is used for handling the start of another main process as a result of a `START_PROJECT` action. The parameter is the uuid of the process instance. The container app is responsible for dismissing the navigation of the current process and displaying the new process navigation.

* `func customLoader(type: CustomLoaderType) -> AnyView?`

This method is used for providing a custom loader view. It is called by the SDK when a loading indicator needs to be displayed. The `type` parameter indicates the context in which the loader is shown. Return `nil` to use the default loader.

This method has a default implementation that returns `nil`.

```swift theme={"system"}
public enum CustomLoaderType {
    case startProcess
    case reloadProcess
    case action(String?)
    case upload(String?)
}
```

* `startProcess` - the loader is displayed when a process is being started
* `reloadProcess` - the loader is displayed when a process is being reloaded
* `action(String?)` - the loader is displayed when an action is being executed. The associated value is the action name, if available.
* `upload(String?)` - the loader is displayed for an in-progress file upload. The associated value is the action name, if available.

#### Sample

```swift theme={"system"}
class MyFXDataSource: FXDataSource {
    func controllerFor(componentIdentifier: String) -> FXController? {
        switch componentIdentifier {
        case "customComponent1":
            let customComponent: CustomViewController = viewController()
            return customComponent
        default:
            return nil
        }
    }
    
    func viewFor(componentIdentifier: String) -> FXView? {
        switch componentIdentifier {
        case "customComponent2":
            return CustomView()
        default:
            return nil
        }
    }
    
    func viewFor(componentIdentifier: String, customComponentViewModel: FXCustomComponentViewModel) -> AnyView? {
        switch componentIdentifier {
        case "customComponent2":
            return AnyView(SUICustomView(viewModel: customComponentViewModel))
        default:
            return nil
        }
    }
    
    func navigationController() -> UINavigationController? {
        nil
    }
    
    func errorReceivedForAction(name: String?) {
        
    }
    
    func validate(validatorName: String, value: Any, params: [String]?) -> Bool {
        switch validatorName {
        case "myCustomValidator":
            let myCustomValidator = MyCustomValidator(input: value as? String)
            return myCustomValidator.isValid()
        default:
            return true
        }
    }
    
    func dismissRequested(forProcess process: String, navigationController: UINavigationController) {
        navigationController.dismiss(animated: true, completion: nil)
        FlowX.sharedInstance.stopProcess(name: process)
    }

    func viewForStepperHeader(stepViewModel: StepViewModel) -> AnyView? {
        return AnyView(CustomStepperHeaderView(stepViewModel: stepViewModel))
    }

    func collect(event: AnalyticsEvent) {
        //TODO: collect event using the desired analytics tool.
    }
 
    func newProcessStarted(processInstanceUuid: String) {
        //TODO present new process instance navigation
    }

    func customLoader(type: CustomLoaderType) -> AnyView? {
        switch type {
        case .startProcess:
            return AnyView(MyStartProcessLoader())
        case .reloadProcess:
            return AnyView(MyReloadLoader())
        case .action(let actionName):
            return AnyView(MyActionLoader(actionName: actionName))
        case .upload(let actionName):
            return AnyView(MyUploadLoader(actionName: actionName))
        }
    }

}
```

### Custom components

#### FXController

FXController is an open class subclassing UIViewController, which helps the container app provide full custom screens the renderer.
It needs to be subclassed for each custom screen.

<Warning>Use this only when the custom component configured in the UI Designer is the root component of the User Task node.</Warning>

```swift theme={"system"}
open class FXController: UIViewController {
    
    internal(set) public var data: Any?
    internal(set) public var actions: [ProcessActionModel]?

    open func titleForScreen() -> String? {
        return nil
    }
    
    open func populateUI() {
        
    }
    
    open func updateUI() {
        
    }
    
}
```

* `internal(set) public var data: Any?`

`data` is the property, containing the data model for the custom component. The type is Any, as it could be a primitive value, a dictionary or an array, depending on the component configuration.

* `internal(set) public var actions: [ProcessActionModel]?`

`actions` is the array of actions provided to the custom component.

* `func titleForScreen() -> String?`

This method is used for setting the screen title. It is called by the renderer when the view controller is displayed.

* `func populateUI()`

This method is called by the renderer, after the controller has been presented, when the data is available.

This will happen asynchronously. It is the container app's responsibility to make sure that the initial state of the view controller does not have default/residual values displayed.

* `func updateUI()`

This method is called by the renderer when an already displayed view controller needs to update the data shown.

#### FXView

FXView is a protocol that helps the container app provide custom UIKit subviews to the renderer. It needs to be implemented by `UIView` instances. Similar to `FXController` it has data and actions properties and a populate method.

```swift theme={"system"}
public protocol FXView: UIView {
    var data: Any? { get set }
    var actions: [ProcessActionModel]? { get set }

    func populateUI()
}
```

* `var data: Any?`

`data` is the property containing the data model for the custom view. The type is Any, as it could be a primitive value, a dictionary or an array, depending on the component configuration.

* `var actions: [ProcessActionModel]?`

`actions` is the array of actions provided to the custom view.

* `func populateUI()`

This method is called by the renderer after the screen containing the view has been displayed.

It is the container app's responsibility to make sure that the initial state of the view does not have default/residual values displayed.

<Warning>It is mandatory for views implementing the FXView protocol to provide the intrinsic content size.</Warning>

```swift theme={"system"}
override var intrinsicContentSize: CGSize {
    return CGSize(width: UIScreen.main.bounds.width, height: 100)
}
```

#### SwiftUI Custom components

Custom SwiftUI components can be provided as type-erased views.

`FXCustomComponentViewModel` is a class implementing the `ObservableObject` protocol. It is used for managing the state of custom SwiftUI views.
It has two published properties, for data and actions. It also includes a `saveData` dictionary and a `validate` closure used for submitting and validating data from the custom components.

```swift theme={"system"}
    @Published public var data: Any?
    @Published public var actions: [ProcessActionModel] = []
    public var saveData: [String: Any]?
    public var validate: (() -> Bool)?
```

Example

```swift theme={"system"}
struct SampleView: View {
    
    @ObservedObject var viewModel: FXCustomComponentViewModel
            
    var body: some View {
        Text("Lorem")
    }
}
```

### Validating SwiftUI Custom Components

A SwiftUI Custom Component can validate and submit data from a custom component, when executing an action from a FlowX.AI UI Component.

* `public var saveData: [String: Any]?`
  Used for setting data to be submitted from the custom component.

* `public var validate: (() -> Bool)?`
  Used for validating the custom component data before executing the action.

#### Sample

```swift theme={"system"}
struct MyCustomView: View {

    @ObservedObject var viewModel: FXCustomComponentViewModel

    var body: some View {
        VStack {
            ...
        }
        .onAppear {
            viewModel.saveData = ["customKey": "customValue"]
            viewModel.validate = {
                return true
            }
        }
        .frame(height: 200)
    }
}
```

### Custom header view for Stepper navigation

The container application can provide a custom view that will be used as the stepper navigation header, using the `FXDataSource` protocol method `viewForStepperHeader`.
The method has a parameter, which provides the data needed for populating the view's UI.

```swift theme={"system"}
public struct StepViewModel {
    // title for the current step; optional
    public var stepTitle: String?
    // title for the current substep, if there is a stepper in stepper configured; optional
    public var substepTitle: String?
    // 1-based index of the current step
    public var step: Int
    // total number of steps
    public var totalSteps: Int
    // 1-based index of the current substep, if there is a stepper in stepper configured; optional
    public var substep: Int?
    // total number of substeps in the current step, if there is a stepper in stepper configured; optional
    public var totalSubsteps: Int?
}
```

#### Sample

```swift theme={"system"}
struct CustomStepperHeaderView: View {
    
    let viewModel: StepViewModel
            
    var body: some View {
        VStack(spacing: 16) {
            ProgressView(value: Float(stepViewModel.step) / Float(stepViewModel.totalSteps))
                .foregroundStyle(Color.blue)
            if let stepTitle = stepViewModel.stepTitle {
                Text(stepTitle)
            }
            if let substepTitle = stepViewModel.substepTitle {
                Text(substepTitle)
            }
        }
        .background(Color.white)
        .shadow(radius: 10)
    }
}
```

### How to run an action from a custom component

The custom components which the container app provides will contain FlowX actions to be executed. To run an action you need to call the following method:

```swift theme={"system"}
public func runAction(action: ProcessActionModel,
                      params: [String: Any]? = nil)
```

`action` - the `ProcessActionModel` action object

`params` - the parameters for the action

### How to run an upload action from a custom component

```swift theme={"system"}
public func runUploadAction(action: ProcessActionModel,
                            image: UIImage)
```

`action` - the `ProcessActionModel` action object

`image` - the image to upload

```swift theme={"system"}
public func runUploadAction(action: ProcessActionModel,
                            fileURL: URL)
```

`action` - the `ProcessActionModel` action object

`fileURL` - the local URL of the file

### Getting a substitution tag value by key

```swift theme={"system"}
public func getTag(withKey key: String) -> String?
```

All substitution tags will be retrieved by the SDK before starting the first process and will be stored in memory.&#x20;

Whenever the container app needs a substitution tag value for populating the UI of the custom components, it can request the substitution tag using the method above, providing the key.

### Getting a media item url by key

```swift theme={"system"}
public func getMediaItemURL(withKey key: String) -> String?
```

All media items will be retrieved by the SDK before starting the first process and will be stored in memory.

Whenever the container app needs a media item url for populating the UI of the custom components, it can request the url using the method above, providing the key.

### Getting enumeration values

The SDK provides two methods for fetching enumeration values, an async/await version and a callback version.

```swift theme={"system"}
public func getEnumeration(name: String,
                           parentName: String?) async -> [EnumerationItem]?
```

```swift theme={"system"}
public func getEnumeration(name: String,
                           parentName: String?,
                           completion: (([EnumerationItem]?) -> Void)?)
```

* `name` - the name of the enumeration to retrieve

* `parentName` - the parent enumeration name, if the enumeration is a child. Pass `nil` for root enumerations.

* `completion` - a closure called with the array of enumeration items, or `nil` if not found (callback version only)

The method first checks the local cache and returns cached items if available. If not cached, it fetches the enumeration from the server.

#### EnumerationItem

```swift theme={"system"}
public struct EnumerationItem: Codable {
    public var type: String?
    public var order: Int?
    public var childContentDescription: EnumerationChildContentDescription?
    public var code: String?
    public var content: String?
}

public struct EnumerationChildContentDescription: Codable {
    public var name: String?
}
```

#### Sample

```swift theme={"system"}
// Using async/await
let items = await FlowX.sharedInstance.getEnumeration(name: "cities",
                                                      parentName: "countries")

// Using completion handler
FlowX.sharedInstance.getEnumeration(name: "cities",
                                    parentName: "countries") { items in
    guard let items = items else { return }
    // use enumeration items
}
```

### Collecting analytics events

To collect analytics events from the SDK, the `collect(event: AnalyticsEvent)` func of the `FXDataSource` protocol needs to be implemented.

The func will be called by the SDK each time a screen or an action analytics event occurs.

The type of the event and relevant metadata are included in the func parameter `event` which is a `AnalyticsEvent` enum case.

```Swift theme={"system"}
public enum AnalyticsEvent {
    case screen(AnalyticsScreenData)
    case action(AnalyticsActionData)
}

public struct AnalyticsScreenData {
    public var value: String
    public var customPayload: [String: Any]?
}

public struct AnalyticsActionData {
    public var value: String
    public var component: String?
    public var label: String?
    public var screen: String?
    public var customPayload: [String: Any]?
}
```

<Info>
  The value property represents the identifier set in the process definition.

  Both screen and action events include an optional `customPayload` dictionary for additional custom data configured in the process definition.

  For action type events there are some additional properties provided:

  * component - The type of component triggering the action
  * label - The label of the component, if available. (E.g. title of a button or label of a form element)
  * screen - The identifier of the screen containing the component, if set
</Info>

### Handling authorization token changes

When the access token of the auth session changes, you can update it in the renderer using the `func updateAuthorization(token: String)` method.

```swift theme={"system"}
FlowX.sharedInstance.updateAuthorization(token: accessToken)
```
