Skip to content

Commit 542285b

Browse files
authored
Merge pull request #213 from valor-software/swiftui-article
Douglas Swiftui article
2 parents 428477b + 59329b4 commit 542285b

7 files changed

Lines changed: 380 additions & 0 deletions

File tree

31 KB
Loading
34.7 KB
Loading
2.4 KB
Loading
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
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, window=_blank], and Apple announced https://developer.apple.com/xcode/swiftui[SwiftUI, window=_blank] at https://developer.apple.com/videos/play/wwdc2019/204/[WWDC19, window=_blank], receiving an immensely positive response from developers.
4+
5+
Here at https://valor-software.com/[Valor Software, window=_blank], we are always excited about new advancements in development technologies, and we are fans of NativeScript. We collaborated with https://nstudio.io/[nStudio, window=_blank] 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, window=_blank], 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+
[.small-img]
176+
image::img2.png[image]
177+
178+
== Advanced SwiftUI Integration
179+
Let's dive deeper by hooking up data bindings + events between SwiftUI and NativeScript.
180+
181+
== Create the SwiftUI component
182+
183+
This can be any SwiftUI you would like to use in NativeScript.
184+
185+
Create `App_Resources/iOS/src/SampleView.swift`:
186+
187+
[, swift]
188+
----
189+
import SwiftUI
190+
191+
class ButtonProps: ObservableObject {
192+
@Published var count: Int = 0
193+
var incrementCount: (() -> Void)?
194+
}
195+
196+
struct SampleView: View {
197+
198+
@ObservedObject var props = ButtonProps()
199+
200+
var body: some View {
201+
VStack(alignment: .center, spacing: 0) {
202+
HStack(alignment:.center) {
203+
Text("Count \(props.count)")
204+
.padding()
205+
.scaledToFill()
206+
.minimumScaleFactor(0.5)
207+
}
208+
HStack(alignment: .center) {
209+
Button(action: {
210+
self.props.incrementCount?()
211+
}) {
212+
Image(systemName: "plus.circle.fill")
213+
.foregroundColor(.white)
214+
.padding()
215+
.background(LinearGradient(
216+
gradient: Gradient(
217+
colors: [Color.purple, Color.pink]), startPoint: .top, endPoint: .bottom
218+
))
219+
.clipShape(Circle())
220+
}
221+
}
222+
}
223+
.padding()
224+
.clipShape(Circle())
225+
}
226+
}
227+
----
228+
229+
Create `App_Resources/iOS/src/SampleViewProvider.swift`:
230+
231+
[,swift]
232+
----
233+
import SwiftUI
234+
235+
@objc
236+
class SampleViewProvider: UIViewController, SwiftUIProvider {
237+
238+
// MARK: INIT
239+
240+
required init?(coder aDecoder: NSCoder) {
241+
super.init(coder: aDecoder)
242+
}
243+
244+
required public init() {
245+
super.init(nibName: nil, bundle: nil)
246+
}
247+
248+
public override func viewDidLoad() {
249+
super.viewDidLoad()
250+
setupSwiftUIView(content: swiftUIView)
251+
registerObservers()
252+
}
253+
254+
// MARK: PRIVATE
255+
256+
private var swiftUIView = SampleView()
257+
258+
private func registerObservers() {
259+
swiftUIView.props.incrementCount = {
260+
let count = self.swiftUIView.props.count + 1
261+
// update swiftUI view
262+
self.swiftUIView.props.count = count
263+
// notify nativescript
264+
self.onEvent?(["count": count])
265+
}
266+
}
267+
268+
// MARK: API
269+
270+
/// Receive data from NativeScript
271+
func updateData(data: NSDictionary) {
272+
if let count = data.value(forKey: "count") as? Int {
273+
// update swiftUI view
274+
swiftUIView.props.count = count
275+
// notify nativescript
276+
self.onEvent?(["count": count])
277+
}
278+
}
279+
280+
/// Send data to NativeScript
281+
var onEvent: ((NSDictionary) -> Void)?
282+
}
283+
----
284+
285+
== Use your SwiftUI in a NativeScript layout
286+
287+
- `app/main-page.xml`:
288+
289+
[, xml]
290+
----
291+
<Page
292+
xmlns="http://schemas.nativescript.org/tns.xsd"
293+
xmlns:sw="@nativescript/swift-ui"
294+
navigatingTo="navigatingTo"
295+
>
296+
<StackLayout>
297+
<sw:SwiftUI swiftId="sampleView" data="{{ nativeCount }}" swiftUIEvent="{{ onEvent }}" loaded="{{ loadedSwiftUI }}" />
298+
<Label text="{{ 'NativeScript Label: ' + nativeCount.count }}" class="h2" />
299+
<Button text="NativeScript data bindings: Decrement" tap="{{ updateNativeScriptData }}" class="btn btn-primary" />
300+
<Button text="SwiftUI data bindings: Decrement" tap="{{ updateSwiftData }}" class="btn btn-primary" />
301+
</StackLayout>
302+
</Page>
303+
----
304+
305+
- `app/main-page.ts`:
306+
307+
[,ts]
308+
----
309+
import {
310+
registerSwiftUI,
311+
UIDataDriver,
312+
SwiftUI,
313+
SwiftUIEventData,
314+
} from "@nativescript/swift-ui";
315+
import {
316+
EventData,
317+
Observable,
318+
Page
319+
} from "@nativescript/core";
320+
321+
// A. You can generate types for your own Swift Provider with 'ns typings ios'
322+
// B. Otherwise you can ignore by declaring the class name you know you provided
323+
declare const SampleViewProvider: any;
324+
325+
registerSwiftUI("sampleView", (view) =>
326+
new UIDataDriver(SampleViewProvider.alloc().init(), view)
327+
);
328+
329+
interface CountData {
330+
count: number;
331+
}
332+
333+
export function navigatingTo(args: EventData) {
334+
const page = <Page>args.object;
335+
page.bindingContext = new DemoModel();
336+
}
337+
338+
export class DemoModel extends Observable {
339+
swiftui: SwiftUI;
340+
nativeCount = {
341+
count: 0,
342+
};
343+
344+
loadedSwiftUI(args) {
345+
this.swiftui = args.object;
346+
}
347+
348+
onEvent(evt: SwiftUIEventData<CountData>) {
349+
this.set("nativeCount", { count: evt.data.count });
350+
}
351+
352+
updateNativeScriptData() {
353+
this.set('nativeCount', { count: this.nativeCount.count - 1 });
354+
}
355+
356+
updateSwiftData() {
357+
this.swiftui.updateData({ count: this.nativeCount.count - 1 });
358+
}
359+
}
360+
----
361+
362+
== iOS screen
363+
364+
video::ou00z5zYcr8[youtube,width=640, height=480, theme=dark]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"title": "Introduction to SwiftUI for NativeScript",
3+
"order": 47,
4+
"domains": ["dev_quality_assurance"],
5+
"authorImg": "assets/articles/introduction-to-swiftui-for-nativescript/douglas.jpg",
6+
"language": "en",
7+
"bgImg": "assets/articles/introduction-to-swiftui-for-nativescript/title_image.png",
8+
"author": "Douglas Machado",
9+
"position": "JS Developer",
10+
"date": "Fri Dec 23 2021 10:45:55 GMT+0000 (Coordinated Universal Time)",
11+
"seoDescription": "We collaborated with nStudio to provide an effective and enjoyable SwiftUI integration for iOS apps driven by NativeScript."
12+
}
2.16 MB
Loading

libs/route-pages/blog-portfolio/src/article.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import { Subscription, of } from "rxjs";
2424
::ng-deep tbody tr:first-of-type {
2525
text-align: center;
2626
}
27+
::ng-deep.small-img {
28+
margin: auto;
29+
width: 40%
30+
}
2731
`],
2832
templateUrl: './article.component.html'
2933
})

0 commit comments

Comments
 (0)