Skip to content

Commit 1621d4b

Browse files
authored
Add files via upload
1 parent 655e618 commit 1621d4b

5 files changed

Lines changed: 363 additions & 0 deletions

File tree

31 KB
Loading
34.7 KB
Loading
2.4 KB
Loading
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
= Introduction to SwiftUI for NativeScript
2+
3+
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 https://developer.android.com/jetpack/compose[Jetpack Compose], and Apple announced https://developer.apple.com/xcode/swiftui[SwiftUI] at https://developer.apple.com/videos/play/wwdc2019/204/[WWDC19], receiving an immensely positive response from developers.
4+
5+
Here at https://valor-software.com/[Valor Software], we are always excited about new advancements in development technologies, and we are fans of NativeScript. We collaborated with https://nstudio.io/[nStudio] to provide an effective and enjoyable SwiftUI integration for iOS apps driven by NativeScript.
6+
7+
In this article, we'll demonstrate how to use SwiftUI within NativeScript to explore fun new possibilities in building amazing UIs together.
8+
9+
== Prerequisites
10+
- macOS Catalina or higher
11+
- Xcode 11 or higher
12+
- iOS device/simulator running iOS 13 or higher
13+
14+
== SwiftUI Concepts
15+
16+
Modern iOS development is primarily done using the Swift programming language. SwiftUI uses a declarative syntax—you state what your user interface should do.
17+
18+
I recommend taking the official https://developer.apple.com/xcode/swiftui[SwiftUI tour], to get familiar with the basic concepts
19+
20+
== Create a NativeScript app
21+
We can create an app using a standard TypeScript template:
22+
23+
[, bash]
24+
----
25+
ns create swiftui --ts
26+
cd swiftui
27+
----
28+
29+
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:
30+
31+
- JavaScript starter
32+
- TypeScript starter
33+
- Angular starter
34+
- React starter
35+
- Svelte starter
36+
- Vue starter
37+
38+
== SwiftUI Plugin
39+
40+
Install the SwiftUI plugin:
41+
42+
[, bash]
43+
----
44+
npm install @nativescript/swift-ui
45+
----
46+
47+
NOTE: Your minimum iOS deployment target should be at least 13.
48+
49+
You can add this line to `App_Resources/iOS/build.xcconfig`:
50+
51+
[, bash]
52+
----
53+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
54+
----
55+
56+
== SwiftUI Usage
57+
58+
. Create your SwiftUI
59+
+
60+
Create App_Resources/iOS/src/SampleView.swift:
61+
+
62+
[,swift]
63+
----
64+
import SwiftUI
65+
66+
struct SampleView: View {
67+
68+
var body: some View {
69+
VStack {
70+
Text("Hello World")
71+
.padding()
72+
}
73+
}
74+
}
75+
----
76+
77+
. Create your SwiftUI Provider
78+
+
79+
This will prepare your SwiftUI for two-way data bindings to NativeScript.
80+
+
81+
Create `App_Resources/iOS/src/SampleViewProvider.swift`:
82+
+
83+
[,swift]
84+
----
85+
import SwiftUI
86+
87+
@objc
88+
class SampleViewProvider: UIViewController, SwiftUIProvider {
89+
90+
// MARK: INIT
91+
92+
required init?(coder aDecoder: NSCoder) {
93+
super.init(coder: aDecoder)
94+
}
95+
96+
required public init() {
97+
super.init(nibName: nil, bundle: nil)
98+
}
99+
100+
public override func viewDidLoad() {
101+
super.viewDidLoad()
102+
setupSwiftUIView(content: swiftUIView)
103+
}
104+
105+
// MARK: PRIVATE
106+
107+
private var swiftUIView = SampleView()
108+
109+
/// Receive data from NativeScript
110+
func updateData(data: NSDictionary) {
111+
// can be empty
112+
}
113+
114+
/// Allow sending of data to NativeScript
115+
var onEvent: ((NSDictionary) -> ())?
116+
}
117+
----
118+
119+
. Insert into any NativeScript layout
120+
+
121+
`app/main-page.xml`
122+
+
123+
[,xml]
124+
----
125+
<Page
126+
xmlns="http://schemas.nativescript.org/tns.xsd"
127+
xmlns:sw="@nativescript/swift-ui"
128+
class="page"
129+
>
130+
<StackLayout>
131+
<sw:SwiftUI swiftId="sampleView" height="100" />
132+
</StackLayout>
133+
</Page>
134+
----
135+
136+
. Register your SwiftUI via the swiftId
137+
+
138+
This can be done in the NativeScript app's bootstrap file (often app.ts or main.ts).
139+
+
140+
- `app.ts`
141+
+
142+
[,ts]
143+
----
144+
import {
145+
registerSwiftUI,
146+
UIDataDriver
147+
} from "@nativescript/swift-ui";
148+
149+
// A. You can generate types for your own Swift Provider with 'ns typings ios'
150+
// B. Otherwise you can ignore by declaring the class name you know you provided
151+
declare const SampleViewProvider: any;
152+
153+
registerSwiftUI("sampleView", (view) =>
154+
new UIDataDriver(SampleViewProvider.alloc().init(), view)
155+
);
156+
----
157+
+
158+
You can now run the app with ns debug ios.
159+
160+
== Use Xcode to develop your SwiftUI
161+
162+
After running the project once, you can open it in Xcode to further develop your SwiftUI using all the convenient aid of Xcode intellisense.
163+
164+
For example from the root of your project:
165+
[,bash]
166+
----
167+
open platforms/ios/swiftui.xcworkspace
168+
----
169+
You will find your .swift code underneath the TNSNativeSource folder as seen here...
170+
171+
image::img1.png[image]
172+
173+
== iOS Preview
174+
175+
image::img2.png[image]
176+
177+
== Advanced SwiftUI Integration
178+
Let's dive deeper by hooking up data bindings + events between SwiftUI and NativeScript.
179+
180+
== Create the SwiftUI component
181+
182+
This can be any SwiftUI you would like to use in NativeScript.
183+
184+
Create `App_Resources/iOS/src/SampleView.swift`:
185+
186+
[, swift]
187+
----
188+
import SwiftUI
189+
190+
class ButtonProps: ObservableObject {
191+
@Published var count: Int = 0
192+
var incrementCount: (() -> Void)?
193+
}
194+
195+
struct SampleView: View {
196+
197+
@ObservedObject var props = ButtonProps()
198+
199+
var body: some View {
200+
VStack(alignment: .center, spacing: 0) {
201+
HStack(alignment:.center) {
202+
Text("Count \(props.count)")
203+
.padding()
204+
.scaledToFill()
205+
.minimumScaleFactor(0.5)
206+
}
207+
HStack(alignment: .center) {
208+
Button(action: {
209+
self.props.incrementCount?()
210+
}) {
211+
Image(systemName: "plus.circle.fill")
212+
.foregroundColor(.white)
213+
.padding()
214+
.background(LinearGradient(
215+
gradient: Gradient(
216+
colors: [Color.purple, Color.pink]), startPoint: .top, endPoint: .bottom
217+
))
218+
.clipShape(Circle())
219+
}
220+
}
221+
}
222+
.padding()
223+
.clipShape(Circle())
224+
}
225+
}
226+
----
227+
228+
Create `App_Resources/iOS/src/SampleViewProvider.swift`:
229+
230+
[,swift]
231+
----
232+
import SwiftUI
233+
234+
@objc
235+
class SampleViewProvider: UIViewController, SwiftUIProvider {
236+
237+
// MARK: INIT
238+
239+
required init?(coder aDecoder: NSCoder) {
240+
super.init(coder: aDecoder)
241+
}
242+
243+
required public init() {
244+
super.init(nibName: nil, bundle: nil)
245+
}
246+
247+
public override func viewDidLoad() {
248+
super.viewDidLoad()
249+
setupSwiftUIView(content: swiftUIView)
250+
registerObservers()
251+
}
252+
253+
// MARK: PRIVATE
254+
255+
private var swiftUIView = SampleView()
256+
257+
private func registerObservers() {
258+
swiftUIView.props.incrementCount = {
259+
let count = self.swiftUIView.props.count + 1
260+
// update swiftUI view
261+
self.swiftUIView.props.count = count
262+
// notify nativescript
263+
self.onEvent?(["count": count])
264+
}
265+
}
266+
267+
// MARK: API
268+
269+
/// Receive data from NativeScript
270+
func updateData(data: NSDictionary) {
271+
if let count = data.value(forKey: "count") as? Int {
272+
// update swiftUI view
273+
swiftUIView.props.count = count
274+
// notify nativescript
275+
self.onEvent?(["count": count])
276+
}
277+
}
278+
279+
/// Send data to NativeScript
280+
var onEvent: ((NSDictionary) -> Void)?
281+
}
282+
----
283+
284+
== Use your SwiftUI in a NativeScript layout
285+
286+
- `app/main-page.xml`:
287+
288+
[, xml]
289+
----
290+
<Page
291+
xmlns="http://schemas.nativescript.org/tns.xsd"
292+
xmlns:sw="@nativescript/swift-ui"
293+
navigatingTo="navigatingTo"
294+
>
295+
<StackLayout>
296+
<sw:SwiftUI swiftId="sampleView" data="{{ nativeCount }}" swiftUIEvent="{{ onEvent }}" loaded="{{ loadedSwiftUI }}" />
297+
<Label text="{{ 'NativeScript Label: ' + nativeCount.count }}" class="h2" />
298+
<Button text="NativeScript data bindings: Decrement" tap="{{ updateNativeScriptData }}" class="btn btn-primary" />
299+
<Button text="SwiftUI data bindings: Decrement" tap="{{ updateSwiftData }}" class="btn btn-primary" />
300+
</StackLayout>
301+
</Page>
302+
----
303+
304+
- `app/main-page.ts`:
305+
306+
[,ts]
307+
----
308+
import {
309+
registerSwiftUI,
310+
UIDataDriver,
311+
SwiftUI,
312+
SwiftUIEventData,
313+
} from "@nativescript/swift-ui";
314+
import {
315+
EventData,
316+
Observable,
317+
Page
318+
} from "@nativescript/core";
319+
320+
// A. You can generate types for your own Swift Provider with 'ns typings ios'
321+
// B. Otherwise you can ignore by declaring the class name you know you provided
322+
declare const SampleViewProvider: any;
323+
324+
registerSwiftUI("sampleView", (view) =>
325+
new UIDataDriver(SampleViewProvider.alloc().init(), view)
326+
);
327+
328+
interface CountData {
329+
count: number;
330+
}
331+
332+
export function navigatingTo(args: EventData) {
333+
const page = <Page>args.object;
334+
page.bindingContext = new DemoModel();
335+
}
336+
337+
export class DemoModel extends Observable {
338+
swiftui: SwiftUI;
339+
nativeCount = {
340+
count: 0,
341+
};
342+
343+
loadedSwiftUI(args) {
344+
this.swiftui = args.object;
345+
}
346+
347+
onEvent(evt: SwiftUIEventData<CountData>) {
348+
this.set("nativeCount", { count: evt.data.count });
349+
}
350+
351+
updateNativeScriptData() {
352+
this.set('nativeCount', { count: this.nativeCount.count - 1 });
353+
}
354+
355+
updateSwiftData() {
356+
this.swiftui.updateData({ count: this.nativeCount.count - 1 });
357+
}
358+
}
359+
----
360+
361+
== iOS screen
362+
363+
video::ou00z5zYcr8[youtube,width=640, height=480, theme=dark]
2.16 MB
Loading

0 commit comments

Comments
 (0)