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 View
s in SwiftUI). These can then be use to compose other View
s.
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 View
s 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 View
s 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:
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.
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"
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>
)
}
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).
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
.
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:
/* 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
<!-- 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.
// Text View with modifiers
Text("Morro Bay")
.font(.title)
// Image View with modifiers
Image("doggy")
.padding()
.shadow(radius: 7)
<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.
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:
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.
import SwiftUI // part of the UI
struct MyView: View {
//
}
import SwiftUI // is part of the UI
// use a class
class MyViewModel {
//
}
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.