Manual iOS SDK framework integrations with NativeScript

It has become very common for app development at any scale to require some type of SDK integration at some point in their lifespan. Let's explore how in NativeScript apps.

Brad Martin
Posted on

SDK (Software Development Kit) integrations

It has become very common for app development at any scale to require some type of SDK (Software Development Kit) integration at some point in their lifespan.

iOS Framework bundles however can be quite frustrating to integrate depending on how the vendor packaged them.

Manual iOS SDK framework integrations with NativeScript

When it comes to iOS, SDK's come in predominantly 3 popular flavors from vendors:

SPM or CocoaPods are often the most convenient and usually easiest to integrate into NativeScript. We won't be covering those in this article as they've been covered quite well in the docs here and at depth in this video course as well as this one.

Framework bundles however can be quite frustrating to integrate depending on how the vendor packaged them. They haven't been documented quite as much with regards to NativeScript outside of the details here which covers some good information and we highly recommend reading it.

We worked on a project that required integrating the Mitek MiSnap iOS SDK for a NativeScript project. Mitek MiSnap™ enables mobile device users to automatically capture images of optimal quality and usability for processing by the Mitek Imaging Platform using an intuitive and easy to use interface. We also cover a completely different .framework gotcha regarding Spotify SDK as well so bear with us, we have a lot to cover here. Let's start with the Mitek SDK.

Initial Integration Attempt

Mitek distributes their iOS SDK as a package of framework bundles you download manually, each of which needs to be integrated. Their documentation provided the following steps:

md
The MiSnapSDK frameworks are required to be part of the Xcode project.
When adding non-iOS standard frameworks to an application they must be linked
and embedded. On the ‘General’ tab, drop down to ‘Embedded Binaries’ and add
the following frameworks from the SDKs/MiSnapSDK folder:
• MiSnapSDK.framework
• MobileFlow.framework
• MiSnapBarcodeScanner.framework
• MiSnapCreditCardScanner.framework

Let's discuss 2 ways we like to try things out with sdk integration work.

Option 1

bash
ns create sample --ts

This will create a quick sample project based in TypeScript to do a quick test if the frameworks will work. Using --ts is very helpful because we can eventually generate typings (TypeScript declarations) for our SDK integration to make working with it in our projects really nice.

You can then create the following structure at the root of the newly generated app while copying the .framework folders into place:

tree
app/
hooks/
nativescript-misnap/
├ package.json
└ platforms/
    └ ios/
        ├ MiSnapSDK.framework
        ├ MobileFlow.framework
        ├ MiSnapBarcodeScanner.framework
        └ MiSnapCreditCardScanner.framework

The contents of nativescript-misnap/package.json can be as simple as follows:

json
{
  "name": "nativescript-misnap",
  "version": "1.0.0",
  "description": "A NativeScript plugin.",
  "nativescript": {
    "platforms": {
      "ios": "8.0.0"
    }
  }
}

Next open the root package.json and add it as a dependency:

json
"dependencies": {
  "@nativescript/core": "~8.5.0",
  "nativescript-misnap": "file:nativescript-misnap"
}

Next open app/main-view-model.ts and right inside the constructor just add the following console.log where we attempt to log out the main ViewController from the MiSnap documentation:

Note

We add declare const MiSnapViewController; to avoid tsc compilation error since we don't have typings yet.

ts
declare const MiSnapViewController

export class HelloWorldModel extends Observable {
  private _counter: number
  private _message: string

  constructor() {
    super()
    console.log('MiSnapViewController:', typeof MiSnapViewController)

    // Initialize default values.
    this._counter = 42
    this.updateMessage()
  }
  // ...
}

Now you can just run the app with ns run ios.

Option 2

Use the NativeScript plugin workspace seed

Follow the seed instructions to set yourself up and then copy the .frameworks into the packages/{name}/platforms/ios directory similar to specified in Option 1.

To confirm whether the sdk frameworks work, open the packages/{name}/index.ios.ts file and add a constructor inside the class where we simply attempt to log out the main ViewController from the MiSnap documentation.

Note

Add declare const MiSnapViewController; to avoid tsc compilation error since we don't have typings yet.

ts
console.log('MiSnapViewController:', typeof MiSnapViewController)

Now run the demo NativeScript app in the workspace (Generally you would use npm start > Then type demo.ios and hit ENTER to run. You can also run the demo directly from root of workspace with npx nx run demo:ios.

The Problem

With both options, the app logged out the following:

ts
MiSnapViewController: undefined

Because MiSnapViewController was not accessible.

At this point, we know something is incorrect with the SDK integration into the NativeScript app. What can we do now?

The Solution

The problem with integrating the SDK was the structure of the framework. The SDK provides iOS StoryBoards for the custom UI and the storyboards contain images as resources for the custom UI.

Looking at the SDK structure, you can see the UX_Resources & UX1_Files directory within the framework. The UX_Resources directory contains the images for the UI and the ViewController for the MiSnapViewController is in the UX1_Files directory.

The solution was to take the files within UX_Resources and put inside a Resources directory in the platforms/ios directory of the plugin. The ViewController files inside UX1_Files was moved inside a src directory in the plugin.

platforms/ios/UX_Resources
platforms/ios/src/UX1_Files

Running ns clean and then running the app now contained the compiled Resources and ViewControllers for the custom UI. When the ViewController was presented we could see the custom UI in the NativeScript app.

Another .framework integration attempt (Spotify)

Spotify released a revised Swift based iOS SDK in 2018 here with this documentation.

The SDK is also a downloaded SpotifyiOS.framework from the repo.

If you follow either Option 1 or 2 above and attempt to do the same with this framework you will quickly get this build error:

Unable to apply changes on device: DBAA5847-A9C4-4FC7-B556-0D4E6589FAA2.
Error is: The bundle at /../SpotifyiOS.framework does not contain an Info.plist file.

Ok so where can we go with this?

The Solution

Based on that error let's simply add our own Info.plist to the framework since it didn't have one.

tree
SpotifyiOS.framework/
└ Info.plist

With the following contents (We can use this setup for a lot of frameworks which complain about this error. Just replace the name with the actual framework name you are integrating):

xml
<?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>CFBundleDevelopmentRegion</key>
  <string>English</string>
  <key>CFBundleExecutable</key>
  <!-- replace this name with your framework name -->
  <string>SpotifyIOS</string>
  <key>CFBundleIdentifier</key>
  <!-- this can be any unique reverse domain identifier -->
  <string>com.spotify.framework.ios</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <!-- replace this name with your framework name -->
  <string>SpotifyIOS</string>
  <key>CFBundlePackageType</key>
  <string>FMWK</string>
</dict>
</plist>

Now our app will actually build. Let's add a console.log of a symbol from the Spotify SDK to see if we have access to it in NativeScript::

ts
declare const SPTConfiguration

console.log('typeof SPTConfiguration:', typeof SPTConfiguration)

If we run the app now, we will see the same problem we had with MiSnap above:

ts
SPTConfiguration: undefined

Yep. The framework integration is not working yet.

Simplify .framework structure

First thing we want to do is simplify the default linking of folders that often come with .framework distributions. In particular we're referring to the little arrow in the bottom left indicating a linked folder structure.

Let's move the folders around to avoid the linked folders and simplify the .framework to reference everything directly ending up with a greatly simplified structure.

Now if we run the app we will still see SPTConfiguration: undefined so we're still not there yet but we're getting closer.

module.modulemap

Next add Modules/module.modulemap file with the following:

ts
framework module SpotifyiOS {
  umbrella header "SpotifyiOS.h"

  export *
  module * { export * }
}

This ensures that NativeScript can find the Headers for the framework which acts as a map to the entire SDK.

Success!

Now if we run the app we will have SUCCESS!! 🎉

ts
SPTConfiguration: function

Wait huh? Yes the fact that NativeScript now sees a function there means it can now access the Spotify SDK.

Generate typings for the SDK

Execute ns typings ios in the root of our sample project (from Option 1 above) or in the demo app from the plugin workspace seed (from Option 2 above). This will generate a typings folder which will have our typings inside for use with our project.

We will find our objc!SpotifyiOS.d.ts typings for our SDK inside!

You can now move that into your plugin folder you were using to integrate the SDK.

Thank you to the {N} team

We specifically want to thank Martin Bektchiev for his expertise with the iOS platform.


More from our Blog