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:
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
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:
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:
{
"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:
"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.
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 .framework
s 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.
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:
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.
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 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::
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:
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:
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!! 🎉
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.