First days with SwiftUI
WWDC’19 has been great and I’m obviously hyper excited about SwiftUI but if there is one thing that I like more than watching a great announcement is watching great video content, and the WWDC sessions are the best!
As every year I’m using my technique to watch the videos. But I haven’t been able to resist and play with SwiftUI!
At the moment of writing this I’m still in Mojave so I’m using SwiftUI by running it in the iOS simulator and in Playgrounds.
Tip: Use
UIHostingController
to display a SwiftUI view in the playground live view.
After doing some simple exercises with SwiftUI it’s quite obvious that is a first beta. Some stuff doesn’t fully work yet and I personally find some crucial controls missing (where is my activity indicator!?).
The example I’ve been working on looks like this:
The List
in SwiftUI is super powerful. You can pass a RandomAccessCollection
to get a view for each model. This is like a simple TableView where each cell represents the same model.
List(data.users) { user in
Text(user.name)
}
This is a great way to get a nice UI, but List
can do so much more! Instead of passing the data in the init
you can just provide each element of the list manually instead. That allows you to make a list with mixed views and content.
List {
Image("swiftui")
.background(Color.black)
Text("First user")
if !data.users.isEmpty {
Text(data.users[0].name)
}
}
Wow! This example is already packing up a lot of stuff. First of all, we just implemented a list with mixed content: an image and one or two texts.
I keep saying List
and that’s important, this is not just a vertical stack of views, this is a fully powered list, with scroll, reusing of the cells and the usual performance niceties that we’re used to from UITableView
.
But that snippet also shows the power of this Swift DSL: we’re using an if
a Swift language construct to create our UI. SwiftUI will take care of rendering the name of the first user when there are users available, and not show it when they’re not.
But what if we want to show all the users? It would be a pain to write them all in there. Ideally we would just use a for in
in the same way we’re using the if
, but as of right now, the Swift feature that allows us to have this nice DSLs doesn’t support for
loops. But wait! SwiftUI fixes this by providing its own ForEach
“view”.
ForEach
accepts a collection and calls the given closure for each element allow you to create multiple views. It’s similar to List
but it’s not intended to be used on its own, as it won’t create a performant scroll for you.
List {
Image("swiftui")
.background(Color.black)
ForEach(data.users) { user in
Text(user.name)
}
}
There you go!
Views are cheap
If you take a look at the example image you will see a list of users and a couple of horizontal carousels.
I was trying to create horizontal lists to replicate an App Store like interface, but for now I haven’t figure out how to build them yet. I think I’m missing a
UICollectionView
Those 3 things are displaying users and so it makes sense to have a view for them (maybe not, but is an example so indulge me). Doing this is as easy as pulling out the code into its own view and passing the data to the init.
Views are struct
so they are really cheap. Their cost in memory is only the cost of the data they have, and SwiftUI will create the best view hierarchy so we don’t have to worry about it.
If you don’t trust me try making some stack of text and check the UI debugger. Stacks are not UIStackView and Text don’t need UILabels.
Let’s make our UsersView
struct UsersView: View {
@Binding var users: [User]
init(users: Binding<[User]>) {
$users = users
}
var body: some View {
Group {
HStack() {
Spacer()
ForEach(users.prefix(4)) { user in
RemoteImage(url: user.avatar, placeholder: Image(systemName: "person.circle.fill"))
.circle()
Spacer()
}
}
.padding(.vertical, 10)
HStack() {
ForEach(users.prefix(4)) { user in
RemoteImage(url: user.avatar)
.circle()
}
}
.padding(.vertical, 10)
ForEach(users) { user in
HStack() {
RemoteImage(url: user.avatar)
VStack(alignment: .leading) {
Text(user.name).font(.title)
Text(user.email)
}
}
}
}
}
}
We return a Group
because we can only return a single View
from the body. A group doesn’t represent anything specific, it just allows us to group a bunch of views into one.
Then you can see HStack
and VStack
. If you’ve done some design with flexbox it will be familiar. For iOS devs this is like a UIStackView
. It just stacks views horizontally or vertically. It’s really flexible and super quick to compose views and make the layout you want.
One thing I’m missing is a “distribution” option or modifier. That’s why I tried 2 ways of replicating it, it works but it’s not really reusable. I hope that watching the rest of SwiftUI sessions will give a better answer ^^
The Spacer
view is quite cool. It doesn’t have any visual representation but instead it fills space in the layout.
I’m super curious to know more about how SwiftUI implementation calculate the layout and renders. It would seem like at some point the layout engine has access to this views and uses them to calculate the frames. I’m interested to know if there is a way to hook into that, so we could replicate a “distribution” property or even implement some “constraints” between views.
The last view that you see is a RemoteImage
. This is a custom view that uses AlamofireImage
to download and cache images. It took me a little bit of effort to make it work. My mistake was not using @State properly and relying on a BindableObject that didn’t work as expected. There is still things to learn here! ^^
Finally the data. We don’t just pass the array of users to the view, we pass a binding to that array. This allows the framework to create the dependency graph, important to know what to refresh and do it in a performant way.
This is the final incarnation of the main view:
struct ContentView : View {
@ObjectBinding var data: DataFetcher
@State var value = true
var body: some View {
List {
Image("swiftui")
.background(Color.black)
Button(action: { self.value.toggle() }) {
Text("The button is \(String(describing: self.value))")
}
.padding([.horizontal], 5)
.background(self.value ? Color.green : .red)
if data.users.isEmpty {
Text("Loading...")
.frame(height: 300)
} else {
UsersView(users: $data.users)
}
}
}
}
It’s not UIKit under the hood
Well, it is in some parts. But this is an IMPLEMENTATION DETAIL!
Really, this is super important to keep in mind, specially in these first days. SwiftUI is not a layer on top of UIKit. SwiftUI is its own UI framework, its own layer of abstraction. What Apple decides to use to render the Views is just details, part of the framework.
If you play around with the UI debugger you will see that a lot of views don’t endup on the screen, the system flattens the hierarchy as much as it can.
While others, like List
are baked by a UITableView
on iOS. But we don’t care about that. It makes sense for Apple to reuse the existing UI infrastructure, specially something like UITableView
with so many years of improvements and so battle tested.
But this is just an implementation detail (yes I”m gonna say it again ^^). On macOS a List
will be another thing, and in watchOS or tvOS. Maybe in November they implemented something different, and maybe next year they release SwiftUI for the web.
This is the beauty of a framework like this, our views are just descriptions of what we want on the screen. The way those descriptions are rendered is not our concern and we can’t rely on that.
An official river
I’m happy I can use the river metaphor for an official framework! Combine looks really cool and pretty familiar if you have any FRP experience. There are many details to learn, and some stuff is not in the betas yet (I’m looking at you URLSession extensions!) but it looks really promising.
It’s quite interesting that they followed the Reactive Streams spec with some changes.
It’s also interesting that they provide not only “steams” (Publisher
) but also a Future
. I imagine that it would become important the day Swift gets async
functionality.
Conclusion
This has been the first post of SwiftUI that I’ve written. There is nothing special on it, nothing you could have not learn by doing the tuto, but I wanted to leave proof on my blog, to myself, of the beginnings of this new era. And after one day of playing with it I had enough in my chest that I could not resist it!
If you’re still reading this, thank you! If you have any insight on the questions I’ve written here please send them over.
The SwiftUI era has begun!