JS to Swift - Prototyping lists with SwiftUI

July 04, 2019

So you want to build a list with Apple's new declarative UI framework. Maybe you're used to building for the web like I am, and you think, "Let's mock up a data structure and iterate over it to make a list." Pretty straightforward, or so you thought. In JavaScript, you might do something like this:

// Mock data structure
const racers = [
	{
		id: 1,
		name: 'Valentino Rossi',
		team: 'Yamaha'
	},
	{
		id: 2,
		name: 'Marc Márquez',
		team: 'Repsol Honda'
	},
];

// In React
<ul>
{racers && racers.map(racer => (
	<li key={racer.id}>{racer.name} rides with {racer.team}</li>
))};
</ul>

// In ES6
const list = document.querySelector('ul');
racers.map(racer => {
	const item = document.createElement('li');
	item.setAttribute('id', racer.id);
	item.textContent = `${racer.name} rides with ${racer.team}`;
	list.appendChild(item);
});

I thought I could do the same thing with SwiftUI. Define an array of dictionaries and iterate over them using something like SwiftUI's ForEach or List views. In UIKit, List would be roughly equal to UITableView, and from my experience with UITableView, the table wants everything to be set up in a very particular fashion. So approaching cautiously, will List require us to do some extra stuff, or can we just chuck some data in and the world will be well and good? Turns out, there's a bit more setup. This won't work:

import SwiftUI

struct RacerList : View {
    
	  // Mock data structure
    let racers: [[String:String]] = [
        [
            "name": "Valentino Rossi",
            "team": "Yamaha"
        ],
        [
            "name": "Marc Márquez",
			      "team": "Repsol Honda"
        ]
    ]
    
    var body: some View {
        List(racers) { racer in
            if let name: String = racer.name, team: String = racer.team {
                Text("\(name) rides with \(team)")
            }
        }
    }
}

The compiler throws this error: Unable to infer complex closure return type; add explicit type to disambiguate , essentially boiling down to: "Hey, I don't understand what type you're returning." But didn't we say the name and team optionals are strings when we unwrapped them?

As it turns out, the problem isn't with the SwiftUI view code, it's with the data structure. Swift is a strongly typed, protocol-oriented language (bold for my own sake). The data you pass into a List needs to conform to the Identifiable protocol so that it knows how to reference each item.

I like to think about protocols like this: protocols are to class inheritance as GraphQL is to REST. No really, hear me out! In regular class inheritance in most object-oriented languages, if you want to extend a class, you get all the methods and properties and other stuff from that class in your new sub-class. With protocols, you split up that baggage into different "slices" that you can tell your new classes to conform to. Much like GraphQL, where you tell the backend exactly what you want and what you don't want, protocols allow you to define exactly what you want your sub classes (or structs) to get.

We can implement the Identifiable protocol like this:

import SwiftUI

// Mock data structure
struct Racer: Identifiable {
    var id: Int
    var name: String
    var team: String
}

struct RacerList : View {
    var body: some View {
        let a = Racer(id: 1, name: "Valentino Rossi", team: "Yamaha")
        let b = Racer(id: 2, name: "Marc Márquez", team: "Repsol Honda")
        let racers = [a, b]
        
        return List(racers) { racer in
            Text("\(racer.name) rides with \(racer.team)")
        }
    }
}

Yay, it works! 🙌 Now if we're to start refactoring this a bit, we can take the data structure, or model as it's known in the iOS world, and put it in a separate directory with all our models. Then, anywhere in our app we define a capital R Racer, the compiler knows we are referencing our model, and hence has detailed information about how it conforms to Identifiable and the type of each property.

Done! This has been another round trip in what may become a loosely associated series of articles about how to do things in Swift coming from JavaScript land. Cheers!

More resources:

Thanks for reading ❤️

If you're jazzed about this post, feel free to tweet this article 🐦

If I missed something, please do drop me a message and I'll fix it 🔨

Otherwise, read more articles! ✍️