Some time ago, I've started working on TaskMeUp for which I wanted to provide a native experience beyond a web one.
I considered lucky that just one year ago, Apple announced SwiftUI, a framework which, on Apple words:
provides views, controls, and layout structures for declaring your app’s user interface.
Something that might sound similar for those of us comming from the web world, since it's quite similar to React (and some other tools and frameworks, of course)
Truth is that SwiftUI takes a lot of inspiration from React and it's quite similar, though adapted to Apple ecosystem and offering an experience similar to what on the web frontend world would be React + (Mobx)[https://mobx.js.org/README.html] + Storybook + some design system (Apple's own on this case).
Starting with SwiftUI
To start working with SwiftUI we need macOS (on the contrary of the web, Apple's ecosystem is not open)
- We open up Xcode
- Select "Create new Xcode project"
- Choose iOS -> App
- We fill in some data, and quite important, select "User interface: SwiftUI"
Xcode will then initialize the project and we will see on its main screen a few files already created. By default, we will have open "ContentView.swift", our first SwiftUI view
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
We can already compile this and run (cmd + r) on a simulator or even on an device (either iOS, iPadOS or macOS). We can also see to the right or the bottom of the editor (depending on our preferences) a preview of our application/view (most likely you will need to click on "resume" for something to happen, this preview stops working with ease).
Let's take a look at what's going on here:
The first line import SwiftUI
is clear, we import that framework (Apple's calls frameworks to all its libraries), bringing to our context all of the names and symbols declared there.
Right after it we see a struct
named ContentView
which implements a protocol
named View
protocols, classes, structs, references, etc
Let's start from the simplest one: a protocol
is similar to what in Typescript or Java is called an interface
. That is to say, a contract, we are stating our struct
will have a set of specific attributes and/or methods. You can't declare types on javascript so there's not an equivalent for this other than documenting (or doing a runtime check) what properties or methods an specific object will have.
By saying that ContentView
implements the View protocol
, we can use ContentView anywhere a View is expected.
Let's move on to the struct
: This is similar to a class
in Javascript and an instance of this struct
is similar to an object. There's one caveat though: on Swift, structs instances will be always passed as values
What does that means? If we pass our object, via a function call or an assigment, this object will be copied and the function or the new symbol will get a copy of it.
On Javascript, objects are always passed by reference, that means that what we pass is actually a pointer to the memory space that holds the object and not the object itself.
let's take a look
On Javascript,
let user = {
name: "Pablo"
}
let anotherUser = user
anotherUser.name = "Emiliano"
console.log(user.name) // "Emiliano"
console.log(anotherUser.name) // "Emiliano"
On Swift,
struct User {
var name: String
}
var user = User(name: "Pablo")
var anotherUser = user
anotherUser.name = "Emiliano"
print(user.name) // "Pablo"
print(anotherUser.name) // "Emiliano"
While it's not present on the code we are analizing, Swift provides class
, which we can say is similar to a struct
which values are passed by reference (on the same way than Javascript).
The syntax is quite similar than the previous example,
class User {
public var name: String
init(name: String) {
self.name = name
}
}
var user = User(name: "Pablo")
var anotherUser = user
anotherUser.name = "Emiliano"
print(user.name) // "Emiliano"
print(anotherUser.name) // "Emiliano"
You can also notice that I had to make two changes: specify the name attribute as public
(by default on classes these are private) and define a constructor (yes, init
method is to Swift's classes what construct
is on Javascript ones)
Let's get back to the initial SwiftUI code. As a sole property of this struct
we have body
. On this case, the ":" (from var body: some View
) are telling us that body
conforms to... some view.
We can read this literally, body is some view, we don't care which one.
Once again this is something that won't have a purpose on javascript or any other not strongly typed language, though a valid question is "what's the different between some View
and just View
if in both cases View
is a protocol
?
Well, some View
is more similar to a generic type. By specyfing some View
we are stating that this variable is of an specific View type, not just any View.
For example, the following example will throw an error
protocol Greeter {
func greet() -> String
}
class Person: Greeter {
func greet() -> String {
return "Hello"
}
}
class User: Greeter {
func greet() -> String {
return "Howdy!"
}
}
// This function is invalid
func test(a: Int) -> some Greeter {
if a > 5 {
return User()
}
return Person()
}
Here test
is trying to return either an User
or a Person, both of them implements
Greeterthough by specifiend that
testreturns
some Greeterwe are saying that it returns an specific type and on our example it can be an
Useror a
Person, not both.
some`, the example will compile successfully.
If we clear the word
Computed properties
body
has more secrets, it immediately opens a curly braces that surrounds a piece of code.
This is known as Computed Properties, which is equivalent to getter and setters methods of javascript.
On this case, since we are not specifying how to assign a new value to body, it's a getter.
`
struct Person {
var name: String
var yearOfBirth: Int
var age: Int {
2020 - yearOfBirth
}
}
var p = Person(name: "Pablo", yearOfBirth: 1987)
print(p.age) // 33
`
What's inside the curly braces is just a function. Swift loves to eliminate redundant code, that's why on single line functions, the result of that line is returned (when you have more lines, you'll need to add return 2020 - yearOfBirth
)
Last but not least (finally!), body
returns Text("Hello world")
. If we do "option + click" on Text, we will see that this is a struct
that implements View
(which was expected since we are declaring that body will return some View
)
Views and Components
We can say that Text("Hello world")
is a component, like React components. SwiftUI knows exactly how to show it, with which style and on what position (even considering if it's running on an iPhone, Apple Watch, Apple TV, etc).
SwifUI provides of course a lot of components and we can also define our owns. Actually on this example, ContentView
is a component just like Text is.
For example, let's surround our Hello World on a List
component
`swift
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
}
}
}
`
And we can see how our application has changed.
We can also make our Text clickable/tapable using a Button
swift
struct ContentView: View {
var body: some View {
List {
Button(action: {
print("Hi")
}) {
Text("Hello, world!")
}
}
}
}
Now everyime we click (or tap) our text, we will see "Hi!" on Xcode debug console.
Worth noticing that all views has methods to change their styling. Eg. we can do Text(...).fontWeight(.bold)
to show that text in bold.
Extra
parameters labels
As you might have seen, when calling a function in Swift, parameters also indicates its names. Swift allow us to define labels on the parameters ans even define different names for the implemenation and the calling:
`swift
getAvatar(for: "Pablo")
func getAvatar(for user: String) {
// ...
// user -> "Pablo"
}
`
I can omit the for
label and use it as getAvatar(user: "Pablo")
or just use an _ to completely omit the name, but it won't read as good.
Functions as last parameter
I'm not exactly sure how this is called, though something that originally confused me on swift, are cases like the one we have in Button above
swift
Button(action: {
print("Hi!")
}) {
Text("Hello, World!")
}
Like Javascript, Swift can use functions as first class citizens, pass them as values and therefore our functions can accept them as parameters. What's courious is that when the function is also the last argument of the function, you can completely omit the label and write the function outside of the parenthesis:
`swift
func doSomething(value: Int, method: (Int) -> ()) {
method(value)
}
doSomething(value: 5) { val in
print(val) // 5
}
`
We can also be explicit of course
swift
doSomething(value: 5, method: { val in
print(val) // 5
})
Final thoughts
SwiftUI has a similar premise than React: Our view is a function of our state and views are composed from other views.
Much of the mindset you used on React can be used on SwiftUI as well.
Xcode also provides a lot of guidelines to help us writing our code as well.
Also, SwiftUI makes it easier to follow Apple conventions and write a decent looking UI that adapts to the system (ie. font changes, dark mode, etc) with almost 0 effort from our side.
The major caveat at this point is that the whole platform is not stable as we would like. On each new OS version (or even Xcode versions) things might breaks on mysterious ways or just behave differently, so I think that while is not strictly required, a deeper understanding of the Apple's previous UI framework UIkit is still valuable when working on mid to large sized apps.
If you have reached this far and you are interested on learning about Swift or SwiftUI, these are some great resources:
- 100 days of SwiftUI by Paul Hudson
- Standford CS193p classes on SwiftUI
- Apple's introduction to SwiftUI on WWDC 2019
- Apple's tutorials on SwiftUI
If you are interested on Software engineering and the web, you can reach me on twitter as @tehsis
Top comments (1)
I have 3 years experience in React and 2 years with Flutter .. This year I changed my career to iOS Dev because of SwiftUI and Swift itself .. It was the best choice I made this year