With NativeScript we have full access to native platform APIs in Java, Kotlin, Objective-C and Swift. All platform languages bring their own unique constructs to the table. With Swift, you can work with it all as long as the construct you're wanting to use exposes it's types to Objective-C.
So how do we integrate third-party Swift code into our NativeScript apps if the Swift construct is not exposed to Objective-C?
Here is the example swift pod that we will use, https://github.com/SwiftKickMobile/SwiftMessages, it allows you to show nifty message cards in a variety of ways.
It is also not usable from objective-c.
Let's create a sample project:
ns create SwiftSample --template @NativeScript/template-hello-world-ts
cd SwiftSample
ns run ios
And we get the lovely tap button screen.
Now we add a reference to the pod in our app:
App_Resources/iOS/PodFile
pod 'SwiftMessages'
At this point we can generate typings so we get strongly typed access to what is in the Pod.
ns typings ios
This will generate a file under typings/ios/<architecture>
.
And we do have a objc!SwiftMessages.d.ts
file, but there is a problem.
If I look at the documentation for SwiftMessages a simple use case is SwiftMessages.show(view: myView)
but where is that in our Types file there is no SwiftMessages class?
The issue here is that the SwiftMessages class is not exposed to Objective-c, and therefore not accessible from NativeScript. More information is available here.
So what is the solution? By the magic of NativeScript we can:
First we add a new Swift file to our project at App_Resources/iOS/src/NSCSwiftMessages.swift
.
And lets add some code (copied from samples on SwiftMessages github) so we can call it from NativeScript.
NOTE: You can use any class prefix here, however we recommend using the NSC
prefix to ensure your class doesn't collide with NS
prefixed classes which are prolific throughout iOS code due to the NeXTStep underpinnings of the entire iOS platform. You can think of NSC
as short for "NativeScript" or "NativeScript Compiler" if that makes it easier to remember. This also helps streamline naming conventions of any platform code you expose this way just for NativeScript purposes.
import Foundation
import SwiftMessages
@objcMembers
@objc(NSCSwiftMessages)
public class NSCSwiftMessages: NSObject {
// We can assign a callback to this from TypeScript.
@objc public var onDoneCallBack: ((String)-> Void)? = nil;
public func showMessage(title: String, body: String) {
// Instantiate a message view from the provided card view layout. SwiftMessages searches for nib
// files in the main bundle first, so you can easily copy them into your project and make changes.
let view = MessageView.viewFromNib(layout: .cardView)
// Theme message elements with the warning style.
view.configureTheme(.success)
view.button?.isHidden=true;
// Add a drop shadow.
view.configureDropShadow()
view.button?.isHidden=true;
// Set message title, body, and icon. Here, we're overriding the default warning
// image with an emoji character.
let iconText = ["🤔", "😳", "🙄", "😶"].randomElement()!
view.configureContent(title: title, body: body, iconText: iconText)
// Increase the external margin around the card. In general, the effect of this setting
// depends on how the given layout is constrained to the layout margins.
view.layoutMarginAdditions = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
// Reduce the corner radius (applicable to layouts featuring rounded corners).
(view.backgroundView as? CornerRoundingView)?.cornerRadius = 10
var config = SwiftMessages.Config()
// Specify one or more event listeners to respond to show and hide events.
config.eventListeners.append() { event in
if case .didHide = event {
// If the callback is setup call it.
self.onDoneCallBack?("Message Alert Hidden");
}
}
// Show the message.
SwiftMessages.show(config: config, view: view)
}
}
The important element here is that we have exposed our swift to Objective-C by the addition of the annotations @objcMembers
& @objc
.
Now we can run the typings generation again and there will be a new file objc!nsswiftsupport.d
In this new file we will see the typings for our new class:
declare class NSCSwiftMessages extends NSObject {
static alloc(): NSCSwiftMessages; // inherited from NSObject
static new(): NSCSwiftMessages; // inherited from NSObject
onDoneCallBack: (p1: string) => void;
showMessageWithTitleBody(title: string, body: string): void;
}
We copy this file to the root of the project (or anywhere that makes sense), and add a refrerence to it in the ./references.d.ts
file.
/// <reference path="./node_modules/@nativescript/types/index.d.ts" />
/// <reference path="./objc!nsswiftsupport.d.ts" />
Now we can call our code, so in app/main-view-model.ts
change the tap method to:
onTap() {
// Create an instance of the Swift class we created.
const message = NSCSwiftMessages.new();
// Assign a callback to get notified when the message closes
message.onDoneCallBack = (message: string) => {this.message = message;};
// Show the actual message
message.showMessageWithTitleBody("This is the Title", "Hello There!");
}
Now when we run the app and tap the button our nice new message shows!
And when it is hidden we get a callback that changes the label.
The sample project is available on GitHub.