Alexito's World

A world of coding 💻, by Alejandro Martinez

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. SwiftUI Playground

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:

SwiftUI example

Mixed lists

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!

If you liked this article please consider supporting me