Slug jordan kay cover?fm=jpg&fl=progressive&q=75&w=300

Painless Type-Safe Data Views in Swift

Swift gives us a clean slate to reimagine the problems we have as iOS developers, allowing us to rethink how an app’s architecture can most take advantage of Swift’s new paradigms. Whatever the language, one of the most common tasks in development is displaying a list of data to the user - in this talk, Jordan Kay introduces his recently re-imagined library Mensa, which lets us easily map data models to UI to display them in a type-safe way in Swift.


Introduction (0:00)

My name is Jordan Kay and I’m an iOS developer based in San Francisco. I spent the last four years working on the iOS team at Twitter. Unfortunately, I was not doing any Swift work so I left about a month ago. Since then, I’ve been writing Swift and playing around with ideas that I’ve had. The one caveat I’ll give is that I don’t know if these are good ideas, but they’ve helped me perform similar tasks in apps I’ve been working on. A lot of these apps rely on displaying a list of data to the user.

Generally speaking, people are set on their patterns and don’t deviate too much from the typical built-in UIKit APIs. Everyone is using UITableViewController or UICollectionViewController and the two protocols that each of those provide. You have some UITableView data source that provides the data to display, and also the UITableViewDelegate protocol which has a lot fun methods.

What I wanted to do was create a higher abstraction on top of those things that reduce the boilerplate we find ourselves writing every time we do one of these things. We’re going to look at this problem at a higher level than overriding or implementing cell for row at indexPath or manually setting up a cell wherever we get it. We’re also figuring out how to configure that cell depending on what type of data we’re displaying.

I’ve seen a lot of different patterns for doing these types of things and wanted to share one that I’ve found useful. With Swift, I feel we can throw out a lot of our old methods of thinking about these types of problems and take advantage of stuff that we couldn’t with Objective-C.

Mensa (3:15)

I’ve recently been working on a library called Mensa. I tried converting this one when Swift came out because I realized how much I’d be able to take advantage of Swift’s type system. In Objective-C, there was really no way of restricting your list of model objects to be of a certain type. There was, but my main goal here was to show if I add something to a list of objects that’s not the type of object I’m looking for, it shouldn’t compile. I found it pretty natural to use Swift to be able to enforce this type safety.

Another reason I started the project was, as I said before, abstracting a way from the bare default UIKit interfaces. I also felt strongly about settling on a pattern for using Interface Builder.

Then the last part was an easy way to map. I wanted a way to map from models to the views that are used to display them. Immediately, it should raise a red flag that seems like a tight coupling. That was one of my main concerns in developing this. I liked being able to think about presenting data that way, but I didn’t want to restrict it too much or introduce unnecessary coupling. What I want to do is show how this new library can make it easy to do some stuff that might be a little bit more complicated if you’re just using standard UIKit APIs. I’m going to have two examples.

Numbers and Primes Example (7:20)

Here’s the first trivial example. I want to make an app that is dealing with numbers and whether or not those numbers are primes. I want to display a list of positive numbers in increasing order, but if one of those numbers is prime, I want to somehow indicate that. When I tap on a non-prime number, it should display the factors of that number. Also, the larger the number, the smaller it should be displayed.

There may be some challenges with this, such as data consistency issues. Essentially, you don’t want data you’re showing to be out of sync with what you have. One of my goals was to make that problem non-existent.

Model View Controller (11:42)

I’m using MVC to architect this. I’m using controllers at a layer where they might not really be expected. Thinking about the data that I have, I know that I’m going to have to have a model and a model class and some way of representing what that object is and the properties that it has. I know that those models will have to be presented using some type of visual view that can take those properties and display them.

Model (12:50)

Here we have our model and we’re going to go back to our example that we had before of displaying the different numbers.

struct Number {
  let value: Int
  lazy var prime: Bool = {
    self.factors.count == 2
  }()

  lazy var factors: [Int] = {
    var factors = Set<Int>()
    ...
    return factors.sort()
  }()

  init(_ value: Int) {
    self.value = value
  }
}

Get more development news like this

I’ve left a few things out here, but I wanted to put this up to get a sense of what type of logic you should see in these types of classes. Swift is great because we can be a lot more lightweight and have our model objects be structs. We don’t have to inherit from anything. Here’s the sort of model logic that we’re used to seeing. Essentially, this is a wrapper around an int but it does provide some of the logic that we’ll need for our app. I left out the part where we go through and find the factors, but that should be straight forward. It’s easy to check if the number is prime if the whole-number factors are 1 and itself.

In our example, we’re displaying flags below prime numbers so we’re going to have a PrimeFlag model.

struct PrimeFlag {
  let number: Number
}

It’s a wrapper around a number. But what’s good about this is once we have access to this, we can look inside the number and that’s how we’ll determine what to show in the text to indicate that a number is prime.

View (15:13)

That was our model. Here’s our view.

class NumberView: UIView {
  @IBOutlet private(set) weak var valueLabel: UILabel!
}

Why do we have a custom sub-class of UIView in order to show something in a table view? Apple would recommend sub-classing UITableViewCell and doing the configuration there, but there is a problem with that relationship. We’d be coupling the display of a number to a UITableViewCell and would be out of luck when coming to collection view controllers where there are no UITableViewCells. What we’re going to do instead is sub-class UIView, which is a little bit closer to this pure view in our MVC layer.

Since we’re using Interface Builder we want to expose the one label that we have which shows the value. The number view is going to show a number in a big or small font.

We have this view and the important part here is that we need to set the custom class identifier to be this number view sub-class. That will allow us to hook up the outlets. We’ll control drag from this thing that’s highlighted, number view, which has these IBOutlets that are ready to be hooked up to our interface. The other thing you’ll notice is that we are using auto layout constraints. That’s going to take care of how this view will size our self and hence, how the table view cells will size themselves in the table view. Regardless of how big this label is, we want to make sure that there’s 10 points from the top, 10 points from the bottom, and at least 10 from the left and right.

Then, here’s where it gets a little more complicated.

class PrimeFlagView: UIView {
  enum Style: Int {
    case Default
    case Compact
  }

  @IBOutlet private(set) weak var textLabel: UILabel?
  private(set) var formatString: String!

  override func awakeFromNib() {
    formatString = textLabel?.text
  }
}

We wanted two different ways of showing this PrimeFlag view. The first one was the full one where you had the two red squares and the label saying what number was prime. The second was the red square, but it should be enough to indicate that the number is prime. I think the most obvious way to represent this is with an enum. We want to scope that enum to PrimeFlag view itself. We don’t want an overarching style enum outside the scope of this because we want a different view and different type of enum to determine how it would be displayed.

We’re going to have two styles, the default one and the compact one where it’s the red square. Then we’ll have our familiar IBOutlets and a format string. That’s going to allow us to take some of the information that we already have in this view and that we’ve put this template here that we’ll fill in later. We have two top-level objects in the .XIB now instead of one. This is a way I found that you could use a single .XIB to represent two styles of views. What this corresponds to is the enum itself.

There’s a reason that this style enum is an int enum where default implicitly is zero and compact implicitly is one. That maps to the indexes of the different view variants that we have. The top is the default at index zero, and the bottom is the compact one at index one. The library is going to read into the .XIB, load the top-level objects, and only use the one that it needs for that variant based on the type that we want to represent.

The constraints that are happening here are similar to the last example. The thing that we want to make sure of is that we have enough room to display everything, so we should provide ample spacing between the content and the inside of its super view. The last constraint is a little different. We’re saying we want this red square to be big enough, but we also want to have enough space surrounding it.

Controller (21:55)

This last part is where some of the proposed ideas start to fall apart, but please bear with me. We’re at the last layer of MVC. For each type, essentially anything that’s not a model or a view is going to go in here. Here I do have a NumberViewController.

class NumberViewController: HostedViewController<Number, NumberView> {
  override func updateView(view: NumberView, withObject number: Number) {
    view.valueLabel.text = "\(number.value)"
  }

  override func selectObject(var number: Number) {
    let factorsString = number.factors.map { "\($0)" }.joinWithSeparator(", ")
    let message = "The factors of \(number.value) are \(factorsString)."
    let alertController = UIAlertController(title: nil, message: message, preferredStyle: .Alert)
    let dismissAction = UIAlertAction(title: "Dismiss", style: .Cancel, handler: nil)
    alertController.addAction(dismissAction)

    presentViewController(alertController, animated: true, completion: nil)
  }
}

We’re displaying a lot of numbers and so a lot of number cells with a view controller for each cell. However, cells are re-used so we’ll have about 11 view controllers opposed to 100. The cell is going to use view controller containment to add this view controller. A NumberViewController is viewed as a NumberView and that’s how we’re going to display a list of numbers. You’ll notice throughout that we’re not sub-classing a UITableViewCell. The NumberedViewController is overriding methods from its super-class and the super-class is a generic type with two type parameters. The first parameter is the object that this view controller is responsible for mediating the display of. The second parameter is the view type. which is the type of view that’s going to be used to display that object.

The selectObject() method is going to handle tapping on any number. It’s using the direct type from this generic hosted view controller super-class. If you used any other type, it wouldn’t compile, which is great because we wouldn’t want a NumberViewController to be controlling any other type. That’s because all of the logic that happens when you select one of these has to depend on some number. We’re taking the number object that’s passed in, so when this method is called, we know what index path it is. Because of that, it’s pretty straight forward to map the section and the row to what backing model object that corresponds to. Whenever this method is called, we’re going to have passed in this number model.

We’re taking this object that was passed in, that we know is a number, getting its factors, joining them into a string that’s separated by commas. Then, we’re presenting this alert view controller to show what those are. What’s cool about this is that this is a real view controller. When we have this alertController, we know exactly how to present it. We’re calling presentViewController() from this and it should show up just fine. That’s what happens when you select one of these, but the real meat of what these view controllers are doing is setting properties of the object to UI elements. We have an updateView method that relies on the types that we’ve defined. updateView is going to be called with two parameters, the first being a view of type that we expect, in this case number view. Second, an object also of the object type that we expect. We’re taking this custom view that we saw before, and setting its label’s text to some textual representation of the number value.

Next is the PrimeFlagViewController.

class PrimeFlagViewController: HostedViewController<PrimeFlag, PrimeFlagView> {
  override func updateView(view: PrimeFlagView, withObject primeFlag: PrimeFlag) {
    view.textLabel?.text = String(format: view.formatString,
    arguments: [primeFlag.number.value])
  }
}

This one’s a little more interesting because we do have two variants of this PrimeFlagView. One of them has a text label and the other doesn’t. Most of the time we treat IBOutlets as implicitly unwrapped optionals because there’s some sort of timing mishap where, when we need them, we haven’t awoken yet. But just as well, we can make those IBOutlets optional if we don’t know this type of view will have that UI element.

The last thing I want to talk about with this example app is how we going to override UITableViewController, how we are going to store the data, and how we say where the data goes.

class ObjectTableViewController: TableViewController<Number, NumberView> {
  private var numbers: [Number] = []

  override var sections: [Section<Number>] {
    return [Section(numbers)]
  }

  required init(style: UITableViewStyle) {
    numbers = (1...numberCount).map { Number($0) }
    super.init(style: style)
  }
}

We have a array of numbers inheriting from a generic class. It has two types, the model type and the view type. We want to show a list of numbers so the model type is Number and the view type is a NumberView. If this array were any other type, this wouldn’t compile because we’re overriding this section’s array, it’s just an array of sections, itself a generic type of these model types. Because this won’t compile, we cannot have a list of a different type that we’re then returning from this method.

The way that we set these numbers up is to go through this range from one to whatever count we want, in this case 100 and map that to an actual number model object. However, there is a problem with this code. It’s missing the PrimeFlags. It’s saying, “I am only responsible for showing numbers, and to show them, I can only use number view.”

Let’s say that both number and PrimeFlag conform to a protocol called object. We know that NumberView and PrimeFlagView both sub-class UIView, so that’s the one we’ll use.

class ObjectTableViewController: TableViewController<Object, UIView> {
  private var objects: [Object] = []

  override var sections: [Section<Object>] {
    return [Section(objects)]
  }

  required init(style: UITableViewStyle) {
    for index in (1...numberCount) {
      var number = Number(index)
      objects.append(number)
      if number.prime {
        objects.append(PrimeFlag(number: number))
      }
    }
    super.init(style: style)
  }
}

Instead of numbers, we’ll have objects. Instead of sections returning a section of numbers, they’re going to return a section of anything that conforms to this object protocol. As we go through this range of numbers, we make our numbers and add a PrimeFlag to our object’s array if that number is prime. Hopefully, you’ll see is that this should nicely map from this array that we have to a way of showing it in a table view.

In order to map model types to view controller types we have to override this registerViewControllers function.

override static func registerViewControllers() throws {
  try registerViewControllerClass(NumberViewController.self,
  forModelType: Number.self)
  try registerViewControllerClass(PrimeFlagViewController.self,
  forModelType: PrimeFlag.self)
  }
}

Behind the scenes this method is keeping a dictionary and using the index path to find the object, get the object’s type and then use it to look up the view controller from the register. The view controller will describe the logic that controls that view.

There is this RegisterError ErrorType which displays if you try to register a totally unrelated view.

enum RegisterError: ErrorType {
  case ViewControllerUnregistered
  case ViewTypeMismatch
  case HostingViewControllerUnderspecified
}

For example, say your TableViewController can only display a PrimeFlagView and then you try to register something that uses a view controller that doesn’t use that.

Quickly, I’m going to talk about the public interface of Mensa before we jump to our real app. This HostedViewController is what NumberViewController and PrimeFlagViewController are sub-classing.

func updateView(view: View, withObject object: Object)
func selectObject(object: Object)
func canSelectObject(object: Object) -> Bool
func setViewHighlighted(highlighted: Bool, forObject: Object)

We saw overriding methods from here. We saw updateView, which is binding the model to the view. And we saw selectObject, which is what happens when you tap on a view based on its represented object. We also have canSelectObject where your view controller can provide logic to objects that have some property you can select or react to.

The last thing I’ll talk about is in our TableViewController.

func didSelectObject(object: Object)
func willLoadHostedViewController(viewController: HostedViewController<Object, View>)
func didUseViewController(viewController: HostedViewController<Object, View>,
  withObject object: Object)
func variantForObject(object: Object) -> Int

We can override for these customization points. The type information gets passed back so we don’t have to do a lot of needless casting and checking. One important function is variantForObject. This makes it easy to say, “In this TableViewController show objects like this and in this TableViewController, show objects like this.” Using our example, if the object passed in is PrimeFlag, the TableViewController will return default and the CollectionViewController will return compact.

Going back to the specification of decreasing the font size, we override didUseViewController, which will pass in the view controller and object that we used.

override func didUseViewController(viewController: HostedViewController<Object, UIView>,
  withObject object: Object) {
  if let number = object as? Number, view = viewController.view as? NumberView {
    let fontSize = CGFloat(maxFontSize - number.value)
    view.valueLabel.font = view.valueLabel.font.fontWithSize(fontSize)
  }
}

Then we have to do some checking because this could be a number or a PrimeFlag. Using the if let syntax, if the object passed in is a number, and the view that the viewController is using is a NumberView, then we can find our font size. Now, we can directly access this value label from the view and set the font size based on the number. We can decrease the font size by taking the max and subtracting the value.

We’ll want to return the compact value in the CollectionViewController only if the object we get back is a PrimeFlag. Otherwise, we can call super.

override func variantForObject(object: Object) -> Int {
  if object is PrimeFlag {
    return PrimeFlagView.Style.Compact.rawValue
  }
  return super.variantForObject(object)
}

Here’s the last part of the library that I’ll talk about before we jump into our real app.

private var backingSections: [Section<Object>] = [] {
  didSet {
    for section in backingSections {
      for object in section {
        let reuseIdentifier = reuseIdentifierForObject(object)
        if metricsCells[reuseIdentifier] == nil {
          if let metricsCell = createMetricsCellForObject(object) {
            metricsCells[reuseIdentifier] = metricsCell
          }
        }
      }
    }
  }
}

Data is stored in these backingSections. As soon as we get an object we haven’t seen before, we create a metricsCell for it based on its variant. That way we can populate that metricsCell with the data that we need and use the layout constraints to find the height.

I won’t get into this, but we’re creating these UITableView cell sub-classes at runtime.

public static func subclassWithViewControllerClass(viewControllerClass: HostedViewController<ObjectType, ViewType>.Type,
  modelType: ObjectType.Type, variant: Int) -> CellClass {
  let bundle = NSBundle(forClass: self)
  let modelTypeName = TypeKey(modelType).localDescription
  let className = TypeKey<Any>(self, viewControllerClass, modelType).description
  var subclass: AnyClass? = NSClassFromString(className)
  if subclass == nil {
    subclass = objc_allocateClassPair(self, className.cStringUsingEncoding(NSUTF8StringEncoding)!, 0)
    let block: @convention(block) AnyObject -> UIViewController = { _ in
      let nibName = modelTypeName + "View"
      let viewController = (viewControllerClass as UIViewController.Type).init()
      let contents = bundle.loadNibNamed(nibName, owner: viewController, options: nil)
      viewController.view = contents[variant] as! UIView
      return viewController
    }
    let implementation = imp_implementationWithBlock(unsafeBitCast(block, AnyObject.self));

    class_addMethod(subclass, "hostedViewController", implementation, "#@:");
    objc_registerClassPair(subclass);
  }
  return subclass as! CellClass
}

Any time we’re going to use a different type of model with a different variant, we’ll guarantee it to be using a different cell sub-class.

Here is the code that’s hosting this view controller.

func loadHostedViewForObject<Object, View: UIView, Cell: HostingCell where
Object == Cell.ObjectType, View == Cell.ViewType>(object: Object, inCell cell: Cell) {
  let hostedView = cell.hostedViewController.view
  hostedView.frame = cell.hostingView.bounds
  hostedView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
  cell.hostingView.addSubview(hostedView)
}

It’s using standard child view controller containment.

Model (39:19)

I wanted to go over how I would architect a real app. I’m calling this one Released. It’s going to do is display a list of the latest movies and albums using the Metacritic API. Here’s the typical type of result that we would get back in JSON.

{
 "results": [
   {
     "rlsdate": "2015-01-26",
     "author": "Gaz Coombes",
     "score": "83",
     "name": "Matador",
   },
   {
     "rlsdate": "2015-01-26",
     "author": "The Phantom Band",
     "score": "TBD",
     "name": "Fears Trending",
   }
  ]
}

Both a movie and an album are a type of media and we know that they’re all going to have a title, and a score, and a release date.

typealias Base = (title: String, score: Int?, releaseDate: NSDate)
protocol Media: Decodable {
  var title: String { get }
  var score: Int? { get }
  var releaseDate: NSDate { get }

  static func baseDecode(j: AnyObject) throws -> Base
}

We know that we have to decode them from JSON.

extension Media {
  static func baseDecode(j: AnyObject) throws -> Base {
    return try (
      j => "name",
      Int(j => "score"),
      dateFormatter.dateFromString(j => "rlsdate")!
    )
  }
}

This is how we’ll represent this list of media.

struct MediaList<T: Media> {
  let items: [T]

  static func resourceWithName(name: String) -> Resource<MediaList> {
    let path = "\(name)/new-releases"
    return Resource(path: path, method: .GET) { data -> MediaList? in
      let json = try! NSJSONSerialization.JSONObjectWithData(
      data, options: NSJSONReadingOptions(rawValue: 0))
      return try? MediaList.decode(json)
    }
  }
}
extension MediaList: Decodable {
  static func decode(j: AnyObject) throws -> MediaList {
    return try MediaList(
      items: j => "results"
    )
  }
}

Here’s how we’ll interact with the API.

typealias FetchAlbumListTask = Task<Float, MediaList<Album>, Reason>
typealias FetchMovieListTask = Task<Float, MediaList<Movie>, Reason>

public struct MetacriticAPI: API {
  public static var baseURL: NSURL {
    return NSURL(string: "https://metacritic-2.p.mashape.com")!
  }

  static func fetchAlbumList() -> FetchAlbumListTask {
    return request(MediaList.resourceWithName("album-list"))
  }

  static func fetchMovieList() -> FetchMovieListTask {
    return request(MediaList.resourceWithName("movie-list"))
  }
}

I highly recommend that you check out SwiftTask. Instead of taking a completion block and a failure block, your API is going to return the task of doing that. Then you can move that logic into methods on that task, like completion and failure. Then we’re essentially doing the same stuff. We have our structs.

Album

struct Album {
  let title: String
  let artist: String
  let score: Int?
  let releaseDate: NSDate
}

extension Album: Media {
  static func decode(j: AnyObject) throws -> Album {
    let artist: String = try j => "author"
    let base = try baseDecode(j)
    return Album(title: base.title, artist: artist, score: base.score, releaseDate: base.releaseDate)
  }
}

Movie

struct Movie {
  let title: String
  let score: Int?
  let rating: String?
  let runtime: String
  let genre: String
  let releaseDate: NSDate
}

They can decode themselves from JSON.

Movie

extension Movie: Media {
  static func decode(j: AnyObject) throws -> Movie {
    let base = try baseDecode(j)

    let runtime: String = try j => "runtime"
    let genre: String = try j => "genre"
    var rating: String? = try j => "rating"
    if rating == "" || rating == "Not Rated" {
      rating = nil
    }

    return Movie(title: base.title, score: base.score, rating: rating,
    runtime: runtime, genre: genre, releaseDate: base.releaseDate)
  }
}

We have our views with the outlets and their constraints set.

Album View

class AlbumView: UIView {
  @IBOutlet private(set) weak var scoreLabel: UILabel!
  @IBOutlet private(set) weak var titleLabel: UILabel!
  @IBOutlet private(set) weak var artistLabel: UILabel!
  @IBOutlet private(set) weak var releaseDateLabel: UILabel!
}

Movie View

class MovieView: UIView {
  @IBOutlet private(set) weak var scoreLabel: UILabel!
  @IBOutlet private(set) weak var titleLabel: UILabel!
  @IBOutlet private(set) weak var descriptionLabel: UILabel!
  @IBOutlet private(set) weak var genreLabel: UILabel!
  @IBOutlet private(set) weak var releaseDateLabel: UILabel!
}

And we have how we update them. We have a Movie View Controller, Album View Controller, and Media View Controller which is the actual list.

Movie View Controller

class MovieViewController: HostedViewController<Movie, MovieView> {
  override func updateView(view: MovieView, withObject movie: Movie) {
    view.titleLabel.text = movie.title
    view.scoreLabel.text = movie.score.map { "\($0)" } ?? "TBD"
  }
}

Album View Controller

private var dateFormatter: NSDateFormatter = {
  let formatter = NSDateFormatter()
  formatter.dateFormat = "MM/d/yyyy"
  return formatter
}()

class AlbumViewController: HostedViewController<Album, AlbumView> {
  override func updateView(view: AlbumView, withObject album: Album) {
    view.titleLabel.text = album.title
    view.artistLabel.text = album.artist
    view.scoreLabel.text = album.score.map { "\($0)" } ?? "TBD"
    view.releaseDateLabel.text = "Released \(dateFormatter.stringFromDate(album.releaseDate))"
  }
}

Media View Controller

class MediaListViewController: TableViewController<Media, UIView> {
  private var items: [Media] = []

  override var sections: [Section<Media>] {
    let sortedItems = items.sort {
      $0.releaseDate.compare($1.releaseDate) == .OrderedDescending
    }
    return [Section(sortedItems)]
  }
}

We have some items of the media type and the sections are taking these items, sorting them by release date, and returning a single section.

We then register each view controller to map from its model type.

Media View Controller

override static func registerViewControllers() throws {
  try registerViewControllerClass(MovieViewController.self, forModelType: Movie.self)
  try registerViewControllerClass(AlbumViewController.self, forModelType: Album.self)
}

Then, we have our fetch album task in viewDidLoad.

Media View Controller

override func viewDidLoad() {
  super.viewDidLoad()

  MetacriticAPI.fetchAlbumList().success { albumList in
    self.items.appendContentsOf(albumList.items.map { $0 })
  }.then { _ in
    MetacriticAPI.fetchMovieList()
  }.success { movieList in
    self.items.appendContentsOf(movieList.items.map { $0 })

    dispatch_async(dispatch_get_main_queue()) {
      self.updateDataAndReloadTableView()
    }
  }
}

We add the albums that we get back and then fetch the movies and add those. Then, we can update the table view to show them all back on the main thread.

Conclusion (41:18)

I think MVC can still be a thing. I wanted to find a way to abstract out of a UIKit and not rewrite the same thing over and over again, essentially providing these customization points when you need them. I wanted to decouple our display of a data model to UITableViewCel. And finally, I wanted Auto Layout to be a straight forward way of laying out our views and being able to see our constraints visually.

Q&A (42:33)

Q: I was wondering about the object when you had the number and the prime and you just made an object. Can we see the code for that? Is it an enum? Is it a protocol?

Jordan: It’s a protocol that’s literally two curly braces. That’s it.

Q: How do you put heterogeneous content into a table view, like you’ve done in your example?

Jordan: I’m sure you’ve run into this “Cannot use protocol with self” constraints. I conveniently didn’t have object have that, so we’re able to do this. If it did have that, I don’t think there’s a way to do that and I think that’s by design.

Q: Then you fall back into the Objective-C pattern of checking what the type of the object is. What is the appropriate solution to that?

Jordan: Other than not having your protocol have associated type constraints, I don’t know of one. Except, other than this library will let you, instead of using a protocol, use a super class, which I guess is another non-Swifty way to look at something, but it should work. If we wanted to, we could have Number and PrimeFlag both sub-class object, then you can put whatever you want in that super-class.

Q: What version of Xcode are you using to compile all of the stuff? We tried multiple times to approach the same challenge and the Interface Builder ignores those classes as soon as you use the generics in any of the parent classes.

Jordan: You can’t expose your generic classes to Interface Builder, so you will have to use some sort of wrapper. This MediaListViewController, there’s a hidden class that you’re not seeing that’s essentially adding this as a Child View Controller. Then that’s what you set as the custom class in IB.

About the content

This content has been published here with the express permission of the author.

Jordan Kay

Jordan Kay is an iOS developer based in San Francisco. He spent the last 4 years working on Twitter’s iOS app, tackling everything from prototyping, to ad products, to crafting reusable UI components. Most recently he’s enjoyed pushing Swift to its limits (read: compiler segfaults). Outside of tech he loves keeping up with the world of drum and bugle corps, and linguistics both computational and non.

4 design patterns for a RESTless mobile integration »

close