WWDC 2016, session 419: Protocol and Value Oriented Programming in UIKit Apps

Posted on October 5, 2016


Local reasoning means that when you look at the code, right in front of you, you don’t have to think about how the rest of your code interacts with that one function.

View layer

Initial cell layout was a UITableViewCell subclass:

class DecoratingLayoutCell : UITableViewCell {
	var content: UIView
	var decoration: UIView

	// Perform layout
}

But this information for the 2 subviews does not need to be held inside a UITableViewCell sublcass, so let’s make it a struct:

struct DecoratingLayout {
	var content: UIView
	var decoration: UIView

	mutating func layout(in rect: CGRect) {
	  // Perform layout
	}
}

So instead of subclassing DecoratingLayoutCell, we use composition:

class DreamCell : UITableViewCell {
	override func layoutSubviews {
	  var decoratingLayout: DecoratingLayout(content: content, decoration: decoration)
	  decoratingLayout.layout(in: bounds)
  }
}

By doing that, we can use exactly the same code in any UIView sublass since the layout logic is decoupled from cells.

But if we want to support different types of content (e.g. SpriteKit nodes instead of views), how can we abstract this further?

struct DecoratingLayout {
	var content: ?
	var decoration: ?

	mutating func layout(in rect: CGRect) {
	  // Perform layout
	  content.frame = ...
	  decoration.frame = ...
	}
}

Since the layout function only sets the frame property of content and layout, we can represent that with a protocol:

protocol Layout {
	var frame: CGRect { get set }
}

and then set the type of content and description to that protocol using retroactive modelling:

struct DecoratingLayout {
	var content: Layout
	var decoration: Layout

	mutating func layout(in rect: CGRect) {
	  // Perform layout
	  content.frame = ...
	  decoration.frame = ...
	}
}

extension UIView : Layout {}
extension SKNode : Layout {}

NOTE: At this point, the layout logic is not using UIKit, which means it can be applied to AppKit as well.

Now, we want content and decoration to be of the same type, so that we can add their children as subviews or subnodes accordingly. In order to accomplish that, we can use generics:

struct DecoratingLayout<Child : Layout> {
	var content: Child
	var decoration: Child

	mutating func layout(in rect: CGRect) {
	  // Perform layout
	  content.frame = ...
	  decoration.frame = ...
	}
}

protocol Layout {
	var frame: CGRect { get set }
}

extension UIView : Layout {}
extension SKNode : Layout {}

Other resources

WWDC 2016, session 419: Protocol and Value Oriented Programming in UIKit Apps

Stay in touch!