Tutorials
Getting started
Chart interface
Web components
Chart internals
Data integration
Customization
Frameworks and bundlers
Mobile development
Trading
Time Span Events
Term structures
ScriptIQ
Troubleshooting
Glossary
Reference
JSFiddles

Getting Started on iOS

In this tutorial you will learn how to integrate the ChartIQ iOS SDK into native iOS apps. You will build the sample native app included in the SDK and instantiate and interact with a ChartIQ chart.

Contents


Overview

The ChartIQ iOS SDK provides a native interface for iOS developers to instantiate and interact with a ChartIQ chart. The interface creates a WKWebView and populates it with a predefined HTML file that loads the ChartIQ JavaScript library, supporting CSS, and a JavaScript “bridge” file which interfaces with the native application.

What occurs in the web view is hidden, automatic, and not dependent on developer configuration. You should be able to instantiate the object as you would any other native component and essentially feel like you are working with a native component.

iOS Quirks

iOS web views do not automatically follow relative paths. You'll need to ensure that your files can be found by iOS. For more information about relative paths in iOS apps, see the Add files and folders to a project tutorial (or search for "Add files and folders to a project" on the Apple xcode help site).

Important

Although the mobile SDK for iOS and the JavaScript library may use the same function call names at times, they are not the same, and they are not interchangeable.

  • When using the mobile SDK, follow the documentation in the mobile native API.
  • When directly calling the JavaScript API, follow the documentation in the JavaScript CIQ core library.

Requirements

See the ChartIQ iOS SDK README.

Download the ChartIQ mobile SDK

Check out the latest version of the ChartIQ iOS SDK from GitHub. If you want a previous version, check out the appropriate tag.

Now, let's create a ChartIQ view in the sample app.

Create a ChartIQ view (optional)

The ChartIQ iOS SDK contains a fully functional sample app. The only thing you need to provide the WKWebView with is an HTML file to load. Whether that is from a web server or files bundled with your iOS application is up to you. The library is set by default to use the Pull mode. So unless you want to change how data is populated in the chart, have other reasons to create your own ViewController, or want to set up specific defaults or chart loading workflow, you can skip this step and go straight to Deploy the ChartIQ mobile SDK.

  1. Import the ChartIQ library into the app's ViewController.

    import ChartIQ
    
  2. Now, in the ChartViewController, add code to let the chart know how to load your data (see the loadChartData function below). In this example we are using the Pull data mechanism. For a Push example, refer to the Data Integration Overview section below.

    
    ...
    
    class ChartViewController: BaseViewController {
        
        ...
    
    extension ChartViewController: ChartIQDelegate {
    
        func chartIQViewDidFinishLoading(_ chartIQView: ChartIQView) {
            chartIQView.setDataMethod(.pull)
        }
    
    }
    
    extension ChartViewController: ChartIQDataSource {
    
        public func pullInitialData(by params: ChartIQQuoteFeedParams, completionHandler: @escaping ([ChartIQData]) -> Void) {
    	    dataSimulatorService.loadChartData(withParameters: params, controller: self) { chartIQDataArray in
    	      completionHandler(chartIQDataArray)
    	      DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
    	        self.chartIQLoadedInitialData()
    	      })
    	    }
    	}
    
        public func pullUpdateData(by params: ChartIQQuoteFeedParams, completionHandler: @escaping ([ChartIQData]) -> Void) {
    		dataSimulatorService.loadChartData(withParameters: params, controller: self, completionHandler: completionHandler)
    	}
    
        public func pullPaginationData(by params: ChartIQQuoteFeedParams, completionHandler: @escaping ([ChartIQData]) -> Void) {
    	    dataSimulatorService.loadChartData(withParameters: params, controller: self, completionHandler: completionHandler)
    	}
    }
    
  3. You now need to make sure your quotefeed url is properly configured in the ChartIQApp/Services/DataSimulatorService.swift file. In this example you might want to change the url and what parameters you pass to your url. For example, our ChartIQ quote simulator needs the following parameters: symbol, start and end dates, period (how many data points to roll into one bar on the chart), interval (type of data), a flag to indicate extended hours, and an unique session id. If you have to adjust your own quotefeed you will have to change the url format accordingly.

Note: The following example uses the ChartIQ quote simulator. You should replace the constant simulatorUrlFormatString in ChartIQApp/Constants/Const.swift with your own implementation.

```swift
class DataSimulatorService {
	...
	internal func loadChartData(withParameters parameters: ChartIQQuoteFeedParams,
                          completionHandler: @escaping ([ChartIQData]?, String?) -> Void) {
    guard let url = getSimulatorURL(withParameters: parameters) else { return }
    let task = URLSession.shared.dataTask(with: url) { data, _, error in
      if let error = error, data == nil {
        completionHandler(nil, error.localizedDescription)
        return
      } else if error == nil, let data = data {
        var chartDatas: [ChartIQData] = []
        do {
          let json = try JSONSerialization.jsonObject(with: data, options: [])
          guard let result = json as? [[String: Any]] else { return }
          result.forEach({ dictionary in
            let chartIQData = ChartIQData(dictionary: dictionary)
            chartDatas.append(chartIQData)
          })
          completionHandler(chartDatas, nil)
        } catch {
          completionHandler(chartDatas, error.localizedDescription)
        }
      }
    }
    task.resume()
  }
  
  private func getSimulatorURL(withParameters parameters: ChartIQQuoteFeedParams) -> URL? {
    var parametersEndDate = ""
    if !parameters.endDate.isEmpty {
      parametersEndDate = String(format: Const.DataSimulatorService.simulatorEndDateFormatString, parameters.endDate)
    }
    if let encodedSymbolParameter = parameters.symbol.stringByAddingPercentEncodingForRFC3986() {
      let urlString = String(format: Const.DataSimulatorService.simulatorUrlFormatString,
                             encodedSymbolParameter,	// IBM
                             parameters.startDate,	// 2019-10-08T04:00:00.000Z
                             parametersEndDate,	// 2021-05-19T18:14:05.200Z
                             parameters.interval,	// day
                             parameters.period,	// 1
                             uuid)
      let simulatorURL = URL(string: urlString)
      return simulatorURL
    }
    return nil
  }
}
```
  1. With the data feed in place you now need a way to stop requesting data. Normally the example quotefeeds provided in our library package do this by setting a moreAvailable flag to tell the quotefeed to retrieve more data depending on the circumstance. The moreAvailable flag is also available within this project but it is accessible within the mobile code itself instead of in our ChartIQ library or native-sdk template. As a developer, you have full control on how to determine when you want to stop retrieving historical data. You can look at the pullInitialDataCallbackMessageHandler, pullUpdateDataCallbackMessageHandler, and pullPaginationDataCallbackMessageHandler methods in ChartIQView.swift for reference. Note that pullUpdateDataCallbackMessageHandler always sets moreAvailable to false since updates use a continuous pooling interval to ensure the chart always displays the most current data.

    internal func pullInitialDataCallbackMessageHandler(message: WKScriptMessage) {
        ...
        dataSource?.pullInitialData(by: params, completionHandler: { [weak self] data in
          guard let self = self else { return }
          DispatchQueue.main.async {
            // set moreAvailable to 'true' if your feed has more data to return after the initial pull. Otherwise, set to 'false'.
            self.formatJSQuoteData(data, moreAvailable: true, cb: cb)
          }
        })
    }
    
    internal func pullUpdateDataCallbackMessageHandler(message: WKScriptMessage) {
        ...
        dataSource?.pullUpdateData(by: params, completionHandler: { [weak self] data in
          guard let self = self else { return }
          DispatchQueue.main.async {
            // Just an update, no need to see if there is more historical data available.
            self.formatJSQuoteData(data, moreAvailable: false, cb: cb)
          }
        })
      }
      
    internal func pullPaginationDataCallbackMessageHandler(message: WKScriptMessage) {
        dataSource?.pullPaginationData(by: params, completionHandler: { [weak self] data in
          guard let self = self else { return }
          DispatchQueue.main.async {
            // Check to see if you need to try and retrieve more historical data.
            // This is where you can put your own logic on when to stop retrieving historical data.
            // By default if the last pagination request return 0 data then it has probably reached the end.
            // If you have spotty data then another idea might be to check the last historical date,
            // this would require you knowing what date to stop at though.
            let moreAvailable = !(data.count < 1)
            self.formatJSQuoteData(data, moreAvailable: moreAvailable, cb: cb)
          }
        })
      }
      
      ...
    
  2. The ChartIQ SDK also provides a listener that will tell the native iOS side that the chart has finished rendering in the WebView. The reason for this is when the WebView is loaded the native iOS code thinks everything is done loading when in reality the chart might still be processing. The chartAvailable listener ensures that you can start to modify the chart with no chance of interference or strange loading artifacts. By default we have it set in the chartAvailableCallbackMessageHandler method in ChartIQView.swift, but feel free to place this call wherever you see fit.

    internal func chartAvailableCallbackMessageHandler(message: WKScriptMessage) {
        guard let chartAvailable = message.body as? String, let isChartAvailable = Bool(chartAvailable) else { return }
        guard isChartAvailable else { return }
        ...
        delegate?.chartIQViewDidFinishLoading(self) // call method in ChartViewController
    }
    
    // ChartViewController.swift
    func chartIQViewDidFinishLoading(_ chartIQView: ChartIQView) {
        // chart has been loaded, now call what you want
        // chartIQView.setChartType(ChartIQChartType.mountain)
        // chartIQView.loadChart("IBM")
        // and so on...
    }
    

Now we're ready to initialize the ChartIQ iOS SDK and connect it to the ChartIQ view.

Deploy the ChartIQ mobile SDK

  1. Finish initializing the mobile SDK in ChartViewController. See the setupChartIQView and chartIQViewDidFinishLoading methods in ChartViewController for examples.

    • Copy sample-template-native-sdk.html from the mobile directory into the root directory of the ChartIQ library package you were provided.

    • In the template script, set the value of the displayDataDisclaimer variable to true if the app uses simulated data; to false, if the app uses production data.

    • Make sure the chart URL string is set to the path where your ChartIQ HTML5 library is deployed so the ChartIQView can be initialized. For the sample application, the URL is set in the ChartIQApp/Constants/Const.swift file.

    • Note: If you are deploying the app using localhost, it is important to know that you have to use your local machine IP address for the URL path. Do not use "localhost" or "127.0.0.1", as the Xcode Emulator or real device won't recognize that path because the app is technically on its own device.

      	static let chartIQURL = "http://deploypath/sample-template-native-sdk.html"
      

    Loading sample-template-native-sdk.html as a local resource

    By default, the sample app is configured to load the sample HTML template and corresponding library files remotely via HTTP.

    If you want to load the library files as local resources, you'll need to ensure that your files can be found by iOS. See the Add files and folders to a project tutorial (or search for "Add files and folders to a project" on the Apple xcode help site).

    Then modify your chart URL to the following:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
         let url = Bundle.main.url(forResource: "chartiq/sample-template-native-sdk", withExtension: "html")
         try! ChartIQView.start(url: (url?.absoluteString)!)
         return true
    }
    

    Alternatively, files can also be loaded using the file:/// scheme, but you will need to bundle the app first to abide by Cross-Origin Resource Sharing (CORS) rules. You can do this using webpack or another module bundler. We include a webpack build specifically for the mobile project in your ChartIQ library package. The steps to create your own bundle are as follows:

    1. Make any necessary changes to chartiq/webpack-example/webpack.config.mobile.js and chartiq/webpack-example/src/sample-template-native-sdk-webpack.js.
    2. Execute npm run build:mobile in the chartiq/webpack-example folder of your library package. The script produces an ES5 bundle which can be used to load files using the file:/// scheme.
    3. A new folder named dist is created in the webpack-example folder; dist contains your bundled library.
    4. By default, the template file is dist/index.html. Change the file name if needed.
    5. Import the dist folder locally into your mobile project. Follow the necessary steps to access index.html

    If you are planning on bundling the library with your application, please let us know so we can send you a suitable package. The standard domain locked library, including the free 30-day trial version, will not work in this case.

    See Sidebar: Advantages of Remote Deployment in the WebViews and HTML5 Containers tutorial for more information.

  2. Build and run the app in an emulator or physical device. You should see a ChartIQ view loaded and populated with a chart.

Data Integration Overview

There are multiple ways to provide your chart with data, but the most common in our ChartIQ library are the Pull and Push integrations. Below are the basic steps you need in order to populate your WebView chart with data for each integration. If you want in-depth details of Pull and Push please refer to the Data Integration Overview tutorial.

Pull Data - The WebView chart requests data from the application via a quotefeed. The example in [Create a ChartIQ View]{#create-a-chartiq-view} is an example of Pulling data.

1. By default, the *sample-template-native-sdk.html* template already has a quotefeed setup in order to receive data from the mobile application. There really shouldn't be a need to modify this functionality as it's just a process that processes the data that is sent by the application to the chart and sends the necessary quotefeed parameters back to the application (startdate, enddate, etc). If you do need to modify this setup for some reason you can do so in *sample-template-native-sdk.html* and *mobile/nativeSdkBridge.js* located in the ChartIQ package that was provided to you.
2. The `refreshInterval` for the quotefeed determines how long to wait before requesting updates. By default the value is set to 1 second. You cannot change this value from your mobile application as making modifications to the quotefeed after it has been created can lead to questionable behavior. You can only change this value in your *sample-template-native-sdk.html* template or in your *js/defaultConfiguration.js* file.

```js
	// sample-template-native-sdk.html
	// modify before you call config.createChart
	config.quoteFeeds[0].behavior.refreshInterval = 10 // 10 second interval
	
	or 
	
	// js/defaultConfiguration.js
	quoteFeeds: [
		{
			quoteFeed: resources.quoteFeed,
			behavior: { refreshInterval: 10, bufferSize: 200 }, // 10 second interval
			// filter: () => {}
		}
	]
```

3. Make sure your mobile application is ready for the quotefeed requests. This involves making sure you have `pullInitialData`, `pullUpdateData`, and `pullPaginationData` available in your application. See step 2 in the Create a ChartIQ View example.
4. Give your application access to data. Usually, this is a URL with the necessary parameters to a service that you get your data from. Access to our quotefeed simulator is delivered out of the box and provides an easy way to see if your chart is working. The simulator provides fake data so you will want to switch this as soon as you can. Once you need to make the switch just change the URL located in *ChartIQApp/Constants/Const.swift*.

Push Data - The mobile application sends data to the WebView chart. This can be done in a number of ways. You can statically load data or have it stream to your application. Essentially you are now the quotefeed and responsible for all its data, the chart will never request data.

1. The chart itself no longer needs a quotefeed, so you can just remove it completely. As with the changing the refreshInterval from the mobile application, this step is only accessible if you change it directly in *sample-template-native-sdk.html*
```js
	const config = getDefaultConfig({
		markerSample: marker.MarkersSample,
		scrollStyle: PerfectScrollbar,
		//quoteFeed: quotefeed,	// a blank chart will now load
		nameValueStore: CIQ.NameValueStore
	});
```
2. You will need to set the data method for the mobile application to Push mode in the `chartIQViewDidFinishLoading` method located in *ChartViewController.swift*.
```swift
func chartIQViewDidFinishLoading(_ chartIQView: ChartIQView) {
	chartIQView.setDataMethod(.push)
}
```
3. Technically you can also remove the code in the mobile application that deals with a quotefeed. That is up to you if you so wish. 
4. Once the chart is ready to receive data you can now Push data via the mobile SDK. You will need to pass a an Array of ChartIQData objects to the chart.
```swift
func chartIQViewDidFinishLoading(_ chartIQView: ChartIQView) {
	chartIQView.setDataMethod(.push)
	
	var data = yourWayToGetData() // where data is in a [ChartIQData] object
	chartIQView.push(data)
}
``` 

Pull/Push Hybrid - There is a common use case where a user wants both data integrations for their application. You might want Pull to retrieve all your historical data and Push for data updates. You basically just combine the methods outlined above with a couple tweaks.

1. Set your quotefeed refreshInteval to 0 in *sample-template-native-sdk.html* or *js/defaultConfiguration.js*. The quotefeed will no longer request updates, but it will still request historical data.
2. Set your data method to Push in your mobile application
```swift
// ChartViewController.swift
func chartIQViewDidFinishLoading(_ chartIQView: ChartIQView) {
	chartIQView.setDataMethod(.push)
}
```
3. Leave all your quotefeed functionality in your mobile application and setup a call to get your Push data.
```swift
func chartIQViewDidFinishLoading(_ chartIQView: ChartIQView) {
	chartIQView.setDataMethod(.push)
	
	var data = yourWayToGetData() // where data is in a [ChartIQData] object
	chartIQView.push(data)
}
``` 
4. That's it! Your chart will now request historial data using the quotefeed and any updates will be handled via your own streaming service.

Extend the ChartIQ mobile SDK

So now that you have the chart working, it will be useful to know how to extend the the mobile SDK. A simple example of how to add your own function to the nativeSdkBridge.js file is in the Getting Started on Mobile tutorial, but how does the iOS native code call the JavaScript functions? The iOS WkWebview has native methods, evaluateJavaScript and evaluateJavaScriptWithReturn, that take a string that represents the call you want to make in JavaScript. The following example uses the files that come with the iOS mobile SDK and ChartIQ SDK.

Let's say you want a method that returns the series object that represents all the series that are currently drawn on the chart.

  1. First modify your nativeSdkBridge.js file which is packaged with your ChartIQ SDK (chartiq/mobile/nativeSdkBridge.js).

    //
    CIQ.MobileBridge.getAllSeries = function () {
        // clone the object if you plan on modifying any of the fields before passing back to iOS
        const allSeries = CIQ.clone(stxx.chart.series);
        return JSON.stringify(seriesObj); // iOS needs to return a string if you are passing back an object
    };
    
  2. Now add a wrapper method to ChartIQView.swift. Since we are returning a value, we need to use the evaluateJavaScriptWithReturn method.

    public func getAllSeries() -> String? {
        let script = "CIQ.MobileBridge.getAllSeries();"
        return webView.evaluateJavaScriptWithReturn(script)
    }
    

    Alternatively, you can call the chart engine JavaScript object (stxx in the example below) directly from the iOS side. This is not encouraged as putting a helper method in the native SDK bridge script allows for easier debugging and less confusion when one script is providing a layer of abstraction.

    public func getAllSeries() -> String? {
        let script = "stxx.chart.series;" // not encouraged
        return webView.evaluateJavaScriptWithReturn(script)
    }
    
  3. This new iOS method can now be called by the ChartIQView object.

    private func messingWithSeries() {
        // series business
        ...
        let allTheSeries = chartIQView.getAllSeries()
        let convertedSeries = try? JSONSerialization.jsonObject(with: data, options: [])
        // now do what you want with the series data
        ...
    }
    

The ability to extend the ChartIQ mobile SDK is powerful, as it gives you access to all the functions that the ChartIQ SDK uses. We cannot one-to-one map all the ChartIQ functions, as that would create a bridge file much too large, but if there are certain functions you absolutely need, we encourage you to extend them in the native SDK bridge.

Next steps

See the following: