Simpler Tables with Values, Enums, & Protocols

More productive Swift table views with Static

samsoffes


Transcript:

I'm Sam Soffes. I'm @Soffes on like every social network. I'm a freelance developer living here in San Francisco. So we're gonna talk about Static. Static is this library I wrote when I was working at Venmo over the summer for making table views more productive but not in the context of “look how fun table views can be”, but more of like interesting Swift features we used when we were making Static. So we're just gonna walk through Static's features and see the interesting things you can do, more as like inspiration to write better Swift code.

00:41: So, very simple to get started, import the framework. Very simple, there's a Section, has some Row’s and then a row has some text. It looks like this. So, pretty straightforward. Here's a slightly more involved example. So there's a header and a footer, there's some text with a detail, an accessory and there's a custom cell. Again, all this is initialization parameters and that looks like this. So, if you think... If you were to make this on your own, you'd need a method for a number of rows, self row next path, the title and the footer and if you did selection it's another... You're already more methods than rows, which is crazy. So, this saves a ton of code, hopefully you can see that.

There are value types, which is amazing, it has a ton of great benefits. I don't have time to go into all of the value type amazingness. Andy has a talk called “Functioning as a Functionalist”, that is spectacular, you should Google it up. But they're values, which has a lot of really nice benefits. For example, we can initialize them mutively and there's a ton of default arguments to the initializer, so we can just leave those out and fill in the properties as we go, which is nice. But how do we get these onto the screen? There is a provided data source. Here's the interface. And it's worth noting this is an object, it's an NSObject? And then it just has a reference to table view and some sections and that's it. There's a couple other utility things but that's the meat of it.

02:19: I think this is interesting 'cause at first I reached for a Struct and this new world of amazing Swift things and trying to do things the Swift way. It's like, of course I'll use the struct, don't need classes at all. In this case it was a really bad use for a struct because it's interfacing directly with table view and that's obviously needed to class, or you need to do tons of hacks like we learned earlier today, so I wanted to avoid that and also there's all this private state just due to the fact of dealing with table views.

In my experience if there's a lot of private state that's unavoidable, it's a good candidate for object because you wanna hide those mutations from your consumer. It's also standalone, so this is another thinking “aha” moment I guess. Originally it was a table view or a view controller subclass, then I tried a table view subclass to make it more flexible, but making it into an object is so much better and thinking of things like the functional Swift way kinda forces you down this road, even if it's something unrelated. This is some class, it's not even a functional thing.

03:23: It also has benefits for it's more composable, it's easier to test. It's just like isolated and separate. There's a couple cases, I've switched them on the fly in the table view for different reasons and that's been really nice. Just having them be standalones has a bunch of nice side effects, so thinking how can I make things more composable has been very productive. So let's look at connecting this to a table view, it's pretty trivial, it comes with a table view controller subclass, that just sets a property and configures it to use the table view. It's like a line of code so you could obviously do this yourself and then we'll just set the sections on the table view, on the data source, excuse me. It's easy enough, looks like that.

Thinking through the basics of Static, row is a view model and that's a really important distinction to me. So if you think we're making the favourite screen from the phone app, we have a list of people models, or contacts or whatever. Now, we're gonna display them in cells but to do that like this backing model may be really complicated or derive from other things. We can make a view model that just represents the state that we're going to display, separate from where it's derived from and how it's going to be displayed. So, we'd have like a person that gets converted into a row that gets displayed by a cell, right? And this is a one-way relationship, which is really important.

04:49: So, the row is not reaching back into the model to get information and the cell is not reaching back into the model, or the view model hopefully, and this lends us some really nice patterns. There's tons of information on the view model way of doing things and I highly encourage you to do some research. It's been super productive. For example, you can make an extension on your model that has a read only property for a row, and then you can simply map your models to view models, which is fantastic. The first time I wrote that I was like, "I'm doing this right”. It's a very good feeling.

Let's go through changing appearance. There's a bunch of supplied cells in Static, and they're just table view cell subclasses, mainly because you can't change the style of a cell after it's been initialized. So, there's just a couple that basically set the style in initialization and that's it. And there's also a ButtonCell, which is what we saw earlier, this bottom one, and basically just sets the text labels text colour to the cell's tint colour, which is a mouthful but it's a pretty simple thing, which is nice 'cause if you change your tint colour of your app, like all of your buttons, your button cells, get updated, which is nice.

06:08: This is possible or all of these conform to the CellType protocol, which is again really simple. Basically, you can just... It configures itself given a view model, and that's it. Notice it doesn't have a property, it's not meant to persist it, it's just gonna like configure itself and be done. And due to default protocol implementations in Swift 2, this is like fantastic, right? So, there's the default implementation, does all this stuff for you and like all the sub classes have a couple lines of code, and like the innit with coder abort stuff, 'cause that's a good time.

So, how would you make a custom cell? Pretty simple, you just subclass, conform to CellType and do whatever you want. So, here we're just changing the colour to red. Notice we didn't implement configure which is great, default protocol implementation. You could if you wanted of course, but you don't have to. So, to use this red cell you just say set the custom class and now you have a red view, it's whatever. At first it was like, "Okay, that makes sense." If I wanna change the appearance I have to make another cell. But I'm used to like, in self row next path, if it's owned, make it whatever colour, if it's unowned like whatever logic you're doing to change the state of your cell in a minor way. So you end up with lots of these slim cells instead of doing that, because with Static you have no notion of cells, you can never even talk to them.

07:40: It's totally abstracted away, which is by design. So I was like, "Okay, I have one for like...  I'm friends with someone and one for I'm not friends with someone”, that seems weird at first, but having all these very slim cells instead of like, a recent project I worked on had a 600-line long self row next path, like not exaggerating, it was just crazy town. I was like, "Okay, that is the opposite end of what I'm trying to do." And I think this works out nicely, because you have your base one, you have a couple sub-classes for variations, and then the model is responsible for saying which cell type to use, and now everywhere else down the chain doesn't matter, right? And this is an inherently model decision like, "How should I go about doing this, right?" So, when it's creating the view model, it can tell the view model, like when it's setting it up, like this is what I want to do. And now, the view controller should not have to know anything about any of this, right? It's just getting it from the view model. So, I think this is a really nice slim, flat separation instead of having tons of... I've definitely worked on projects before that cells are hundreds of lines long with tons of logic about the model that's also duplicated other places 'cause it needs to get displayed in text instead of in a cell or something. So, this is a nice way to go about that.

Let's talk about accessories, not that exciting but hopefully it can be. So, here's the system accessories, right, there's all the different types, and then theres' Custom View as well. And at first I was just implementing parts of table view that I needed to work on Static when I was first writing it. Originally, I had an accessory view that was optional, an accessory type that was optional, and I had properties for both. And that seemed like a perfectly logical way to do it, that's how table view cell does it. And then it occurred to me that a much cleaner way to do this would be to make it just another part of this enum, because they're mutually exclusive. And that was another pattern I've used a lot since then, realizing if you have two optional properties that are mutually exclusive, having an enum to represent that is a much cleaner way to express the same piece of information.

09:47: So, you can see at the end we have a View that contains a UIView, straight-forward enough. And you’ll also notice that some of these have associated selections, so that's for like the i button in favourites like we showed earlier. If you type the i it shows the contact card and if you tap the row it calls the person. There's two variations of that from the system. So, Selection I think is another... As I was designing this API I was like, "this is great, I feel happy about this." This is a Selection, it's very complicated, it's just a function, doesn't do anything, right? That's a really important distinction, we'll see in a second. So it's just, you can.. selection do this, and this is tracked internally, right? 'Cause it's just a property on the view model, so when the table view detects a selection it can figure out which view model that was and do all that, but all the mapping from index path to row is like private in the data source 'cause you shouldn't have to ever worry about index paths. But since it's just a function, we can do some pretty cool things. So, we can just say show details and implement whatever thing that does something.

So, you're making like a setting screen with ‘notification settings’ and ‘edit my email’ or whatever, you're just like- a line of code for each row, and that has a selection and you have a nice little instance method for each tap action, instead of like some giant switch statement in didSelect. Which is awesome, big fan of that. But how would we say, back to the phone example, I have a list of people and I want to call someone. How do I know which person you tapped if there's no argument? And at first I was like I'll pass the row in as the selection argument, but the row doesn't have a reference to the model, and that's by design, so that doesn't really help me. How should I like go about this? And actually I didn't need to build anything, it's just like the way functions work, which is great. So, now when I'm building my rows from people I can just say, "Okay, I'll map people to rows and that makes a, I'll make a mutable version of that, and I can just add on to that and set the selection of that struct and now return the struct."

Now I have a selection, this is pretty cool. And I know now, 'cause it prints the name, right? So, I don't have to worry about passing this around or keeping some look-up table or getting an array of models at a particular index path, like none of that, right? It's just all done for you. And again, you could make this work with an instance method, a little cleaner. But this is really unfortunate that you have to deal with weak self and like nested blocks and that's not great, but it's interesting that we're making a function that returns a function. But I don't even really think of it as a function, right? It's just a selection, a selection is a function, but I'm working with this type, right? Not necessarily functions. But this is actually exactly the code I want, it's just kind of ugly. But you can actually do this, make this a lot nicer with currying, which is sort of what that's doing, it’s just there's nice syntax built in to Swift for it. So, that exact same code can look like this, just add the extra parenthis after the arguments. And now, it's going to return for you a function that's callable implicitly which is great.

13:06: And the calling code is exactly the same, there's no difference, which is awesome. I wrote that I was... I got up from my desk and was like, "Alright, that was... I am a Swift developer. That was awesome." I was so proud of myself, it was great, right? So I just wanted to talk about prototyping for a minute. Say we're making the settings screen or options, excuse me, from Instagram. If you were to sit down and implement this, and there's switches and other controls down at the bottom, this would take some time. If you're doing it in static table views and IB it would take some time, or if you were going to do it in a design tool and save out screens and make some clickable prototype with whatever took, like sure that's also going to take some time. With Static you can think like that's a line of code per thing. You can set an image in Static so you don't have to worry about that. And there was a button cell like, "Okay, this would take you two minutes at most, if you're a slow typer, right?" It's pretty great. And things I've worked on in the past, especially Venmo and clients after that, it was a lot of table-view based flows. At least at first, they probably get redesigned on the road, but a sign-in thing, and then like find friends and whatever, or something flow into your account, all of that's just table views in a lot of cases. At least in the sort of applications I've been working on.

Doing this in Static was faster than any sort of prototyping tool. To take it from a whiteboard to production code was like a matter of minutes versus at least an hour to do a clickable prototype with any other method. Which is interesting, right? Like other PMs or designers are like, "Man, they have super-powers." And it's like no, I just have this really good tool. So, hopefully that's inspiring and not from the sense of like, "Cool, look at these neat table views." But using, structs and enums when appropriate and protocols, and all these nice Swift features, to make something as trivial as like a table-view wrapper... But since working on this, lots of code from libraries, from networking to whatever else to application code, I've used a lot of these principles and felt dramatically more productive and have that same, "Yeah, I'm a Swift developer!" Like I feel like I'm doing it the Swift way, and that's like this, like sure it's this nice feeling that's like alright I like nice feelings, great. But it's also like really productive and maintainable.

15:47: So, I would encourage you to try to think from the sense, and not that anyone is doing this, but I know when I first started Swift I was like, "How can I convert my Objective-C code to this nice syntax," right? But then going another level to how could I think, the Swift way of doing things is like a whole other step, right? And I feel like using these new features and generics and whatnot as well, like you can write some really great stuff that's not hard to read or complicated, it's just like different than you're used to, and it can be pretty great. So, hopefully that's some examples of that. All this code is open source, Venmo, there's people that work there that still maintain it. Some of them are here today. I'm also working on it here and there, and I'm using it in production in several client apps. I know they're using it in production and other companies as well. I'd encourage you to give it a read, it's not long or complicated. It's actually very short. It's just some clever usage of structs and enums and whatnot to wrap a table view. It's not rocket science. I'm @soffes on like everything, so say hi. I will take any questions, thanks.

Q&A (17:00):


Q1: Really great talk, really awesome looking library. This is also something that I'm working on a lot, or struggling with in terms of trying to have very dry modular code that works with either two label views or collection views and trying to abstract away the data sources. Might be hinting in the name but does your library also handle more dynamic table views like stuff that's coming in asynchronously, or say if I need to download an image from the network and update that cell?

Sam: Sure, so getting an image from the network is outside of the scope of it. That's like a responsibility for the cell, but you can definitely, like I've definitely been using it in client apps with dynamic data. That said, all of your data has to be in memory due to its design. So if your set is unbounded, it's not a good candidate. But if you're gonna have up to a couple thousand things, they're just lightweight structs, right? Go nuts. Right, but if it's like some infinite scroll thing, it's not really for that, but it could be extended easy enough to have a delegate pattern instead of setting the properties directly. It'll just be a lot more complicated. And the whole point was static at first, but it definitely does dynamic stuff, to some extent.

Q2: Hey Sam, great talk. We've been doing a lot of table views lately, so I would’ve liked to have known this a couple days ago or weeks ago. What are some of the limitations that you always get to this. 'Cause, sometimes, I mean, these frameworks work great for these kind of examples, but suddenly, you try to get to the edge, and you gotta start hacking around it. Can you handle, for example, text fields, and user input?

Sam: Sure. Yeah. I've definitely built several log in screens with this, or sign up, or have switches for settings. But a lot of those details get pushed down to your views, and the cell's gonna be responsible for those, and not your view controller, which is nice. There's another parameter on row, called context, that takes a string in an any dictionary. So sometimes, they pass in extra meta information, so the cell can react to like, "You did some input, like, do something”, or use delegation when possible. There's definitely, within the abstraction like this, you're gonna get to the edge, for example, reordering or editing is unsupported, but it's static, it’s not really for that. But, yeah, you can absolutely work with user input, if you want.