Use SwiftUI with NativeScript.
- @nativescript/swift-ui
npm install @nativescript/swift-ui
Note: you will need to target iOS 13 at a minimum. For example, you can add this line to your
App_Resources/iOS/build.xcconfig
:
IPHONEOS_DEPLOYMENT_TARGET = 13.0
Note: If you would like to use
NativeScriptView
inside SwiftUI, you should target 14.0 minimum.
This can be any SwiftUI view you'd like to create.
import SwiftUI
struct SampleView: View {
var body: some View {
VStack {
Text("Hello World")
.padding()
}
}
}
This will prepare your SwiftUI for two-way data bindings to NativeScript and follows the plugins' SwiftUIProvider
protocol to standardize all SwiftUI bindings.
import SwiftUI
@objc
class SampleViewProvider: UIViewController, SwiftUIProvider {
// MARK: INIT
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
required public init() {
super.init(nibName: nil, bundle: nil)
}
public override func viewDidLoad() {
super.viewDidLoad()
setupSwiftUIView(content: swiftUIView)
}
// MARK: PRIVATE
private var swiftUIView = SampleView()
/// Receive data from NativeScript
func updateData(data: NSDictionary) {
// can be empty
}
/// Allow sending of data to NativeScript
var onEvent: ((NSDictionary) -> ())?
}
This can be done in the bootstrap file (often app.ts
or main.ts
) or even the view component that needs it.
import {
registerSwiftUI,
UIDataDriver
} from "@nativescript/swift-ui";
// A. You can generate types for your own Swift Provider with 'ns typings ios'
// B. Otherwise you can ignore by declaring the class name you know you provided
declare const SampleViewProvider: any;
registerSwiftUI("sampleView", (view) =>
new UIDataDriver(SampleViewProvider.alloc().init(), view)
);
registerSwiftUI("barChart", (view) =>
new UIDataDriver(BarChartProvider.alloc().init(), view)
);
Then insert it in any layout as follows:
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" xmlns:sw="@nativescript/swift-ui">
<StackLayout>
<sw:SwiftUI swiftId="sampleView" height="150" />
</StackLayout>
</Page>
-
To generate types for your SwiftUI code, run
ns typings ios
. -
Locate the
objc!nsswiftsupport.d.ts
file intypings/x86_64
and reference it in thereferences.d.ts
.
Register SwiftUI follows:
import { registerElement } from '@nativescript/angular'
import { SwiftUI } from '@nativescript/swift-ui'
registerElement('SwiftUI', () => SwiftUI)
registerSwiftUI("sampleView", (view) =>
new UIDataDriver(SampleViewProvider.alloc().init(), view)
);
registerSwiftUI("barChart", (view) =>
new UIDataDriver(BarChartProvider.alloc().init(), view)
);
It can now be used within any Angular component, eg:
<StackLayout class="p-20">
<SwiftUI swiftId="sampleView" height="150"></SwiftUI>
</StackLayout>
Register SwiftUI follows:
registerElement("SwiftUIView", ()=> require("@nativescript/swift-ui").SwiftUI)
registerSwiftUI("sampleView", (view) =>
new UIDataDriver(SampleViewProvider.alloc().init(), view)
);
registerSwiftUI("barChart", (view) =>
new UIDataDriver(BarChartProvider.alloc().init(), view)
);
Then use it in markup as follows:
<StackLayout>
<SwiftUIView swiftId="sampleView" height="200" />
<SwiftUIView height="500" swiftId="barChart" margin="30" />
</StackLayout>
Register SwiftUI follows:
registerSwiftUI("sampleView", (view) =>
new UIDataDriver(SampleViewProvider.alloc().init(), view)
);
registerSwiftUI("barChart", (view) =>
new UIDataDriver(BarChartProvider.alloc().init(), view)
);
interface SwiftUIViewAttributes extends ViewAttributes{
swiftId: string
}
declare global {
module JSX {
interface IntrinsicElements {
swiftUIView: NativeScriptProps<SwiftUIViewAttributes, SwiftUI>
}
}
}
registerElement("swiftUIView", ()=> require("@nativescript/swift-ui").SwiftUI)
Then use it in markup as follows:
<stackLayout>
<swiftUIView swiftId="sampleView" height="200" />
<swiftUIView height="500" swiftId="barChart" margin="30" />
</stackLayout>
When using a SwiftUI App Lifecycle setup for your NativeScript app, the default with visionOS development, you can enable multiple scenes in your Info.plist
with the following:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationPreferredDefaultSceneSessionRole</key>
<string>UIWindowSceneSessionRoleApplication</string>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
You can now use WindowManager
(for use with standard Windows) or XR
(for use with immersive spaces) to interact with multiple scenes, for example:
@main
struct NativeScriptApp: App {
@State private var immersionStyle: ImmersionStyle = .mixed
var body: some Scene {
NativeScriptMainWindow()
WindowGroup(id: "NeatView") {
NeatView()
}
.windowStyle(.plain)
ImmersiveSpace(id: "NeatImmersive") {
NeatImmersive()
}
.immersionStyle(selection: $immersionStyle, in: .mixed, .full)
}
}
You could open the WindowGroup
with:
import { WindowManager } from "@nativescript/swift-ui";
WindowManager.getWindow("NeatView").open();
});
And you could open the ImmersiveSpace
with:
import { XR } from "@nativescript/swift-ui";
XR.requestSession("NeatImmersive");
You could update either scene with:
import { WindowManager } from "@nativescript/swift-ui";
// Option A: inline
WindowManager.getWindow("NeatView").update({
title: 'Updated Title'
});
// Option B: reference
const neatView = WindowManager.getWindow("NeatView");
neatView.update({
title: 'Updated Title'
});
// Both options work with XR/Immersive Spaces as well, for example:
WindowManager.getWindow("NeatImmersive").update({
salutation: 'Hello World'
});
You can use the onReceive
modifier in SwiftUI to handle any data passed to your windows.
For example, anytime WindowManager.getWindow("Window_ID").update(...)
is called, a Notification is dispatched which can be picked up for data to be handled:
struct NeatView: View {
@State var context: NativeScriptWindowContext?
var body: some View {
ZStack {
// more neat views here
}.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("NativeScriptWindowUpdate")), perform: { obj in
context = NativeScriptWindowFactory.shared.getContextForId(id: "NeatView")
let title = context!.data["title"] as! String
// use your updated title!
})
}
}
WindowManager.getWindow("NeatView").close()
for a Window which is already open will close it.
XR.endSession()
for an Immersive Space which is already open will close it.
You can also use NativeScript view layouts and components inside SwiftUI when targeting iOS 14.0 minimum.
Add this line to your App_Resources/iOS/build.xcconfig
:
IPHONEOS_DEPLOYMENT_TARGET = 14.0
You can now register as many NativeScript views by an id for usage:
import { SwiftUIManager } from '@nativescript/swift-ui';
SwiftUIManager.registerNativeScriptViews({
Video: SceneVideoComponent
});
This will allow SceneVideoComponent
, a NativeScript view component, to be used inside any SwiftUI component:
struct ContentView: View {
var body: some View {
ZStack {
NativeScriptView(id: "Video")
}
}
}
- WindowManager and XR APIs were established with the Callstack team. Shoutout to: Oskar Kwaśniewski.
NativeScript is proudly supported by Valor Software as an official partner. We are proud to offer guidance, consulting, and development assistance in all things NativeScript.
MIT