Skip to main content

Learning SwiftUI (As A React Developer)

· 12 min read
Erik

I built my first iOS app in 2012 for a competition called 59 Days of Code. Back then the only way to build one was by collectively using Objective-C, UIKit, and optionally the Storyboard visual builder. I never became an expert at it since it wasn't something I wanted to do professionally — after all web technologies had always been my bread and butter. Nonetheless, I learned enough iOS development to get things working and eventually deploy an app to the AppStore. Since then the ecosystem has changed.

First, we've seen the rise of iPhone apps being developed with Swift (in lieu of Objective-C), UIKit, and Storyboard. This new trifecta wasn't any more simpler, but certainly friendlier than using Objective-C. I still recall how weird Objective-C was. Then in 2019 the iOS landscape changed once again.

Swift and SwiftUI Have Entered the Chat

The modern way to build iPhone apps (and also iPad/Watch/Mac apps) is with the Swift programming language, which was introduced in 2014, and the SwiftUI framework. SwiftUI was announced in 2019, but only has worked for iOS 13 and newer since it's only compatible with Swift 5.1+ which wasn't available until iOS 13.

From time to time, I have read about and experimented with Swift and SwiftUI, but not extensively. The most recent time I dabbled with both Swift and SwiftUI that I can remember was in 2020, when SwiftUI was still in its infancy. I did not want to invest too much time on it since I knew it was too new to be broadly adopted and used yet.

This goes to say that I have never actually built an app using Swift and SwiftUI. At least, one never made it past one screen before I would throw in the towel. It has been about 3 years since the release of SwiftUI. iOS 13 will soon be the oldest iOS supported which means we can pretty much start using SwiftUI as the defacto framework. Though SwiftUI is not perfect yet, I finally feel like it's the right time for me to learn it. It just feels right.

Learning Swift and SwiftUI

To kick off my learning, I began spending time on the Swift Language Guide documentation. Skimming through several programming constructs, syntax, and concepts. Comparing and contrasting it with programming languages I already know. I am familiarizing myself with the various programing patterns and styles offered by Swift.

I have been reading about the different data structure and types, control flow, functions, and all that good stuff that comes with a programming language. In addition, I'm taking the free Stanford CS193p - Developing Apps for iOS course on YouTube, which I highly recommend. Then I spent some time going over the Apple provided visual SwiftUI tutorials which are actually a great starting point. After that it'll just be me working on a real-world app that I've been wanting to build for a while!

Declarative Programming is Here to Stay

Using Objective-C, Storyboard, and UIKit requires a lot of tedious work since we have to describe how it should do things, known as imperative programming. A lot of low-level cruft and detailing – ugh. The opposite of imperative programming is declarative programming. These are just programming styles. There are programming languages like Javascript that offer both styles.

To give an example of imperative versus declarative programming, I'll use my favorite language, JavaScript. Let's say we want to print out the elements in an array one at a time. There are several ways to do that, but below I offer two ways. Notice how one requires a bit more effort on our part:

const animals = ["cat", "dog", "duck", "chicken"];

// the HOW should it do it (imperative)
for (let i = 0; i < animals.length; i += 1) {
const animal = animals[i];
console.log(animal)
}

// the WHAT should it do (declarative)
animals.forEach((animal) => {
console.log(animal);
});

Declarative programming was popularized by functional programming languages. Nowadays, most programmers can agree that declarative programming is the better approach. It removes complexity and it tends to make code read more like English, which increases code readability. Though some will say it comes at a cost of performance. Though most compilers and build systems can be optimize to handle this trade-off.

The declarative nature is what makes frameworks and libraries like React popular, since they're easier to code, and it's certainly why Apple decided to make SwiftUI declarative in contrast with UIKit.

Advantages of Knowing React, HTML, and CSS Before Learning SwiftUI

I have been developing React and React Native apps for almost two years now. Practically on a daily basis. And I've been using HTML and CSS for almost two decades. I haven't yet gone in depth with SwiftUI, but just at the surface level I am already seeing a lot of commonalities between React (HTML + CSS) and SwiftUI.

Being able to compare React to SwiftUI has significantly flattened the learning curve for me. In many cases they have a one-to-one relation. Practically SwiftUI is to Swift, what React is to JavaScript.

I am convinced that learning Swift and SwiftUI this time around will be so much easier. It's my understanding that SwiftUI borrows several concepts from React and why the similarities exist, so this can only benefit me as a current React developer.

Let's take a closer look at some of the similarities.

Reactive UI based on State

Like React, SwiftUI user interface reacts to state changes. SwiftUI does this by using the @State decorator, tokens that precede with a @ are known as a property wrappers. Structs are often destroyed and recreated, the @State is able to maintain its state during this process. It's similar to a local state, though it's preferable to use data from the model along with the @Published, which basically is an use of the observer pattern.

The @Bind property wrapper can be used to synchronized "props" (to put lightly) that are passed down to other "components" (Views) and keep them in sync with the source of truth (state).

Component-like Offerings

One thing I like about React is how we can break down pieces of UI into their own components. With SwiftUI we can also extract sections of code into re-usable "components" (known as Views in SwiftUI). These can then be use to compose other Views.

A View just happens to be the name of a Swift protocol defined in SwiftUI. They are similar to interfaces in other languages like TypeScript, but a lot closer to Abstract Classes in Java.

SwiftUI has several built-in Views such as Text, Image, HStack, VStack, ZStack, LazyVGrid, LazyHGrid, GridItem, Spacer, ScrollView, Circle, Map, Divider, and a ton more! They all conform to the View protocol, therefore they are all considered Views which means they behave like any other View. We can compose Views from other Views! Here are examples of composition in both React and SwiftUI:

App.jsx
function MyCustomComponent({ name }) {
return (<span>Hello {name}! How are you?</span>);
}

function App() {
return (
<div style={{ padding: "10px", display: "flex", flexDirection: "column" }}>
<h1>My Cool App</h1>
<MyCustomComponent name={"Erik"} /> {/* Use custom component */}
</div>
)
}

With SwiftUI, returning a group of Views happens is possible due to result builders.

ContentView.swift
struct ContentView: View {
var body: some View {
VStack {
MyView(name: "Erik") /* Use custom view */
}
.padding()
}
}

struct MyView: View {
var name: String

var body: some View {
Text("Hello \(name)! How are you?")
}
}

Conditional "Rendering"

App.jsx
function App() {

const isHappy = true;

return (
<div style={{ padding: "10px", display: "flex", flexDirection: "column" }}>
<p>{isHappy ? <span>Happy! 😃</span> : <span>Not happy ☹️</span>}</p>
</div>
)
}
ContentView.swift
struct ContentView: View {

var isHappy: Bool = true

var body: some View {
if isHappy {
Text("Happy! 😃")
} else {
Text("Not happy ☹️")
}
}
}

Generating Lists

To generate lists in React we can simply use a JavaScript array map method and always provide it a key to disambiguate (if we don't index is used by default which is not ideal).

Component.jsx
function Pets() {
const pets = ["duck", "chicken", "dog", "cat", "pony"];

return (<ul>
{pets.map((pet) => {
return (<li key={pet}>{pet}</li>)
})}
</ul>);
}

With Swift it's going to be similar, but instead we use a ForEach struct. We also have to provide a key, however it's instead called an id.

ContentView.swift
struct ContentView: View {
var pets: [String] = ["duck", "chicken", "dog", "cat", "pony"];

var body: some View {
ForEach(pets[0..<pets.count], id: \.self) { pet in
Text(pet)
}
}
}

Similarities with HTML and CSS

We can also compare several SwiftUI concepts to CSS and HTML. For example, the following Views are similar to some HTML and CSS properties:

ContentView.swift
/* 1 */
Text("Howdy")

/* 2 */
Image(name: "doggy")

// These next 3 are known as Combiners

/* 3 */
HStack { /* content */ }

/* 4 */
VStack { /* content */ }

/* 5 */
ZStack { /* content */ }
// there's also a .zIndex modifier
index.html
<!-- 1 -->
<span>Howdy</span>

<!-- 2 -->
<img src="doggy.jpg" alt="">

<!-- 3 -->
<div style="display: flex; flex-direction: row;"><!-- content --></div>

<!-- 4 -->
<div style="display: flex; flex-direction: column;"><!-- content --></div>

<!-- 5 -->
<div style="z-index: 1;"><!-- content --></div>

We can also simulate CSS display: grid; by using LazyVGrid, LazyHGrid, and GridItem Views. In addition we can modify the GridItem to take arguments like .adaptive to behave like flex wrap to fit as many items per row as available based ont the device screen size or orientation.

Another SwiftUI pattern is called modifiers (which are basically chainable methods/functions), which can modify a View's behavior and/or appearance. Modifiers themselves return a modified version of a View.

example.swift
// Text View with modifiers
Text("Morro Bay")
.font(.title)

// Image View with modifiers
Image("doggy")
.padding()
.shadow(radius: 7)
example.html
<span style="font-family: san-serif;">Morro Bay</span>

<img src="doggy.jpg" alt="small dog" style="padding: 10px; box-shadow: 0 0 3px 10px #000; border-radius: 7px;">

Modifiers are similar to some HTML attributes and some CSS properties. In fact certain modifiers will get inherited by nested views similar to how CSS inheritance works, for example the .font() and .foregroundColor() modifier will affect nested Views as well. Though in nested Views they can be overridden if needed, again just like in CSS.

Handling Events

In SwiftUI most events are basically gestures. There are several gestures that are already built-in. For example when something is tapped the generalize gesture modifier is onGesture or dragged (onDrag), amongst many others. And to handle them we just need to attach them as View modifiers.

There's also more specific gesture handlers, for example onTapGesture. In the following SwiftUI example we have two images that when tapped on they just print out a corresponding message.

ContentView.swift
struct ContentView: View {

var body: some View {
VStack {
Image(systemName: "house").onTapGesture {
print("a house!")
}

Image(systemName: "pencil.tip").onTapGesture {
print("a pencil!")
}
}
}
}

In React it would be similar to the following component code:

App.jsx
function App() {

return (
<div>
<img src="house.jpg" onClick={(e) => {
console.log("a house!");
}} />
<img src="pencil-tip.jpg" onClick={(e) => {
console.log("a house!");
}} />
</div>
)
}

Adding Icons

For icons, when we use React (or vanilla HTML) many times we'll rely on FontAwesome or other third party icon packages. In the Apple ecosystem we have thousands of free icons available via the SF Symbols package, which can be used with with the Image view i.e. Image(systemName: "house.fill").

Architectural Design Patterns

In React there's really no specific architectural pattern or one particular way to organize components and data. It's overall un-opinionated and up to the developer to adopt a pattern that works. After all that's why React is considered a library, not a framework.

With SwiftUI we have more guidance in that the recommended way to do things is to follow the MVVM design pattern. This differs from the previous way with Objective-C, which followed the MVC pattern.

As previously mentioned, we can setup local state with SwiftUI using the @State property wrapper. However, that should only be used if we need ephemeral data. The data instead should always live in the Model (just a struct). The ModelView (can be a Class or Struct) can be setup to conform the ObservableObject protocol. Doing this will provide access to objectWillChange.send() to notify a change, but better yet we can use @Published instead to do it for us.

Assuming we can to build a basic app, typically we would have three associated files for each feature. You would probably want better names the ones used in the examples. Take note of the imports and the usage of struct versus class in each file.

MyView.swift
import SwiftUI // part of the UI

struct MyView: View {
//
}

MyViewModal.swift
import SwiftUI // is part of the UI

// use a class
class MyViewModel {
//
}

MyModel.swift
import Foundation

struct MyModel {
//
}

Conclusion

It's been a while since I developed a native iPhone app, but I'm ready to do it again, alas with Swift and SwiftUI this time around. I think it's still valuable to know Objective-C, Storyboard, and UIKit since it will still be supported for many years and many company apps out there still have production apps built with it. We can't fully move away from Objective-C/UIKit just yet, but at least now we have another reliable and broadly supported option. I think it's a good time to learn Swift and SwiftUI right now. So far based on what I've experienced during my learning journey I can recognize that knowing React, HTML, and CSS has well positioned me to make the process so much easier.