Building user interfaces declaratively is something the Web community has widely adopted, and nowadays, large applications are built following these principles. For example, Google launched Jetpack Compose, and Apple announced SwiftUI at WWDC19, receiving an immensely positive response from developers.
Here at Valor Software, we are always excited about new advancements in development technologies, and we are fans of NativeScript. We collaborated with nStudio to provide an effective and enjoyable SwiftUI integration for iOS apps driven by NativeScript.
In this article, we'll demonstrate how to use SwiftUI within NativeScript to explore fun new possibilities in building amazing UIs together.
PrerequisitesModern iOS development is primarily done using the Swift programming language. SwiftUI uses a declarative syntax—you state what your user interface should do.
I recommend taking the official SwiftUI tour, to get familiar with the basic concepts.
Create a NativeScript appWe can create an app using a standard TypeScript template:
ns create swiftui --ts
cd swiftui
Enter fullscreen mode Exit fullscreen mode
This will setup what is often called a "vanilla" flavored NativeScript app. In other words, it provides basic data binding capabilities and a rather simple setup. However, what we will cover here applies to any flavor (Angular, React, Svelte, Vue, etc.). You can explore more via StackBlitz from the following:
SwiftUI PluginInstall the SwiftUI plugin:
npm install @nativescript/swift-ui
Enter fullscreen mode Exit fullscreen mode
Note: Your minimum iOS deployment target should be at least 13.
You can add this line to App_Resources/iOS/build.xcconfig
:
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
Enter fullscreen mode Exit fullscreen mode
SwiftUI UsageA. Create your SwiftUI
Create App_Resources/iOS/src/SampleView.swift
:
import SwiftUI
struct SampleView: View {
var body: some View {
VStack {
Text("Hello World")
.padding()
}
}
}
Enter fullscreen mode Exit fullscreen mode
B. Create your SwiftUI Provider
This will prepare your SwiftUI for two-way data bindings to NativeScript.
Create App_Resources/iOS/src/SampleViewProvider.swift
:
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) -> ())?
}
Enter fullscreen mode Exit fullscreen mode
C. Insert into any NativeScript layout
app/main-page.xml
<Page
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:sw="@nativescript/swift-ui"
class="page"
>
<StackLayout>
<sw:SwiftUI swiftId="sampleView" height="100" />
</StackLayout>
</Page>
Enter fullscreen mode Exit fullscreen mode
D. Register your SwiftUI via the swiftId
This can be done in the NativeScript app's bootstrap file (often app.ts
or main.ts
).
app.ts
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)
);
Enter fullscreen mode Exit fullscreen mode
You can now run the app with ns debug ios
.
After running the project once, you can open it in Xcode to further develop your SwiftUI using all the convenient aid of Xcode intellisense.
For example from the root of your project:
open platforms/ios/swiftui.xcworkspace
Enter fullscreen mode Exit fullscreen mode
You will find your .swift
code underneath the TNSNativeSource
folder as seen here...
Let's dive deeper by hooking up data bindings + events between SwiftUI and NativeScript.
Create the SwiftUI componentThis can be any SwiftUI you would like to use in NativeScript.
Create App_Resources/iOS/src/SampleView.swift
:
import SwiftUI
class ButtonProps: ObservableObject {
@Published var count: Int = 0
var incrementCount: (() -> Void)?
}
struct SampleView: View {
@ObservedObject var props = ButtonProps()
var body: some View {
VStack(alignment: .center, spacing: 0) {
HStack(alignment:.center) {
Text("Count \(props.count)")
.padding()
.scaledToFill()
.minimumScaleFactor(0.5)
}
HStack(alignment: .center) {
Button(action: {
self.props.incrementCount?()
}) {
Image(systemName: "plus.circle.fill")
.foregroundColor(.white)
.padding()
.background(LinearGradient(
gradient: Gradient(
colors: [Color.purple, Color.pink]), startPoint: .top, endPoint: .bottom
))
.clipShape(Circle())
}
}
}
.padding()
.clipShape(Circle())
}
}
Enter fullscreen mode Exit fullscreen mode
Create App_Resources/iOS/src/SampleViewProvider.swift
:
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)
registerObservers()
}
// MARK: PRIVATE
private var swiftUIView = SampleView()
private func registerObservers() {
swiftUIView.props.incrementCount = {
let count = self.swiftUIView.props.count + 1
// update swiftUI view
self.swiftUIView.props.count = count
// notify nativescript
self.onEvent?(["count": count])
}
}
// MARK: API
/// Receive data from NativeScript
func updateData(data: NSDictionary) {
if let count = data.value(forKey: "count") as? Int {
// update swiftUI view
swiftUIView.props.count = count
// notify nativescript
self.onEvent?(["count": count])
}
}
/// Send data to NativeScript
var onEvent: ((NSDictionary) -> Void)?
}
Enter fullscreen mode Exit fullscreen mode
Use your SwiftUI in a NativeScript layoutapp/main-page.xml
:<Page
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:sw="@nativescript/swift-ui"
navigatingTo="navigatingTo"
>
<StackLayout>
<sw:SwiftUI swiftId="sampleView" data="{{ nativeCount }}" swiftUIEvent="{{ onEvent }}" loaded="{{ loadedSwiftUI }}" />
<Label text="{{ 'NativeScript Label: ' + nativeCount.count }}" class="h2" />
<Button text="NativeScript data bindings: Decrement" tap="{{ updateNativeScriptData }}" class="btn btn-primary" />
<Button text="SwiftUI data bindings: Decrement" tap="{{ updateSwiftData }}" class="btn btn-primary" />
</StackLayout>
</Page>
Enter fullscreen mode Exit fullscreen mode
app/main-page.ts
:import {
registerSwiftUI,
UIDataDriver,
SwiftUI,
SwiftUIEventData,
} from "@nativescript/swift-ui";
import {
EventData,
Observable,
Page
} from "@nativescript/core";
// 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)
);
interface CountData {
count: number;
}
export function navigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = new DemoModel();
}
export class DemoModel extends Observable {
swiftui: SwiftUI;
nativeCount = {
count: 0,
};
loadedSwiftUI(args) {
this.swiftui = args.object;
}
onEvent(evt: SwiftUIEventData<CountData>) {
this.set("nativeCount", { count: evt.data.count });
}
updateNativeScriptData() {
this.set('nativeCount', { count: this.nativeCount.count - 1 });
}
updateSwiftData() {
this.swiftui.updateData({ count: this.nativeCount.count - 1 });
}
}
Enter fullscreen mode Exit fullscreen mode
iOS screen About Valor Software:Founded in 2013 by Dmitriy Shekhovtsov (Dima) as a MEAN stack software development firm, Valor has evolved into a globally renowned software development and consulting firm with over 130 individuals working on Full Stack development with a primary focus on all things TypeScript from 5 continents.
Media Contact: sales@valor-software.com
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4