Project Micro – Part 1

I’ve had a few personal projects in the works for quite some time now, and they haven’t been working out. The cycle generally begins with enthusiasm and a grand vision, followed by inevitable scope-creep, culminating in the realization that the project has grown far beyond my capabilities alone. The obvious answer would be collaboration, but given my existing obligations and maladroit project management, I’d prefer not to rope anyone else into the project without a stronger roadmap.

The last project to be put on hold was an iOS shooter that I quickly realized required quite a bit more artwork than my non-artist self could provide. Turns out, a game where you fight giant robots with multiple destructible parts and reconfigurable weapons requires a talented character artist. After a while, I found it disheartening to work on. Fighting a cube in a gray-box level isn’t particularly exciting, and I found myself missing the forest for the trees. I enjoy systems-oriented work and would get bogged down, spending weeks building plumbing for a house not yet connected to the water main. The boss can’t die yet, but it runs on a custom hierarchical behavior-tree system with a full visual editor, so that’s neat, I guess.

I’ve decided it’s time for a change of pace. I need a project with a tighter control on scope. A project with a more focused end goal, and a project which doesn’t necessarily require an entire team of artists to complete. Thinking through these requirements, I came up with an idea.

Project Micro

Project micro is a physics based rogue-ish survival game, where the player must construct and evolve a virtual creature, battling for supremacy in the primordial ooze. The player initially designs a simple creature using cells connected by links. There are many types of cells and links, each of which serve a different purpose as a component of a player’s creation. Players must navigate through the (very limited) environment and consume as many rival creatures as possible. At fixed time intervals, the player is invited to edit their creature, making changes as they see fit. The severity of these changes is dependent on their success (eat more opponents, change more rapidly).

Opponents function identically to the player, but are controlled by a procedurally evolved neural network. Initially, opponents will be generated using a few seed creatures, and will flounder through the water aimlessly. Each time an opponent is consumed, the current most successful AI is cloned, mutated, and spawned elsewhere on the map. The vision for the project is a continually evolving ecosystem, where opponents become increasingly effective and aggressive, until they inevitably kill the player.

The goal is to construct a micro-scale arms race, where players must constantly update their strategy in order to survive opponents which are forever adapting to the game state. While this idea isn’t necessarily new (I admit, it’s heavily inspired by the early stages of Spore), I think building a much more dynamic game could really be interesting, both from a gameplay and design perspective. It also exists as an almost entirely systems-driven experience, interacting nicely with my skill set and keeping the scope relatively small.

Here We Go…

So now I’ve got an idea, and I’m off to the races! I’m also jumping on the old “gamedev blog” bandwagon and will “try to post regular status updates” as things come together. Now, if you’ll excuse me, I’m off to build a physics engine.

Elastic UITableView Headers (Done Right)

Elastic or “stretchy” table headers are all over the place. They add that extra juice to a polished app, and provide a nice solution for the otherwise revealing native scroll view bounce in iOS. I’m talking about one of these…

Don’t forget that translucency thing Apple seems so fond of these days! A good header implementation should properly underlap the translucent navigation bar, and auto-adjust view margins to push content within the safe area! It’s subtle, but notice how the background image extends under the navigation bar, matching the behavior of other native views!

So graceful! So fluid! Just look at that Auto Layout spring! Clearly, this is a must-have. Ask any iOS developer, and they’ll happily explain their own favorite way of implementing an elastic header. Unfortunately, many of these techniques are “icky”.

The most common implementation is to have the UITableViewController handle the layout and positioning of the header, usually in the viewDidLayoutSubviews() method. This should be setting off all kinds of alarms. While it’s a quick and easy solution, it relies on a ViewController to dictate the layout and display of data… that’s kind of the point of a View, isn’t it? The ViewController is ideally responsible for the formatting of data, so that it can later be presented in a view. By handling the table header layout in the ViewController, we’re tightly coupling the implementation of the controller with the UI design of the app. The ViewController shouldn’t care whether the header is elastic or static. All it should care about is assigning the data for the table to display.


So what’s the plan?

We need to build a nice re-usable table view which does what we want, but maintains a consistent interface with a “normal” table view. This way, it can be a drop-in replacement for all the UITableView instances in your app. ViewControllers shouldn’t care about layout.

Therefore, we need a UITableView subclass which…

  • features an elastic header.
  • has an identical interface to UITableView
  • plays nice with UIKit (nav-bars, safe area, etc.)
  • can be loaded from a Storyboard

Let’s start off nice and simple. A new TableView subclass.

import Foundation
import UIKit

class ElasticHeaderTableView : UITableView {
    override func layoutSubviews() {
        // Obviously, something goes here!
    }
}

We’ve immediately hit a problem! Table views already have a tableHeaderView property which has all sorts of fancy logic attached to it! Any UIView you assign to this field will mess with the content insets, tweak the scroll rect, and generally wreak havoc on our layout! Since we’re implementing custom layout logic, we can’t just update the rect of this view! We also can’t ignore it, since our target was to provide an identical interface to a standard UITableView!

This is a great opportunity to do some overriding! How about we write a new get/set, and forward the value to a new field? This effectively allows us to bypass the superclass’s implementation, since (as far as the Obj-C class definition is concerned), the stored value in the synthesized tableHeaderView field will always be nil.

override var tableHeaderView: UIView? {
    set {
        _elasticHeaderView = newValue
    }
    get {
        return _elasticHeaderView
    }
}

private var _elasticHeaderView: UIView? {
    willSet {
        _elasticHeaderView?.removeFromSuperview()
    }
    didSet {
        if let headerView = _elasticHeaderView {
            addSubview(headerView)
            bringSubviewToFront(headerView)
            
            updateHeaderMargins()
            let headerSize = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            contentInset.top = headerSize.height - safeAreaInsets.top
            contentOffset.y = -headerSize.height
        }
    }
}

private func updateHeaderMargins () {
    _elasticHeaderView?.directionalLayoutMargins = NSDirectionalEdgeInsets(
        top: safeAreaInsets.top,
        leading: safeAreaInsets.left,  
        bottom: 0,
        trailing: safeAreaInsets.right)
}

You’ll notice some observer blocks attached to the _elasticHeaderView. The first is a willSet. This will remove the existing header from the view hierarchy, so we don’t leave it around if the header view is changed! The second is a didSet block. This will add the new header subview, calls a a method called “updateHeaderMargins”, and adjusts the content inset for the tableView to account for the new header height, so our table view rows don’t overlap the header. updateHeaderMargins() will simply copy the safe area of the tableView into the header’s directional layout margins. This allows constraints to the view margin to factor in the portion of the tableView obscured by nav bars, or the iPhone X “notch”. While not “necessary” for many applications, this makes life a lot easier as you start using the class.

Alright, now we need to actually put things where they need to go! Every time the tableView is adjusted using Auto Layout or the scroll view scrolls, the elastic header must be adjusted to account for the new space. Let’s define a new function for that.

override var contentOffset: CGPoint {
    didSet {
        layoutHeaderView()
    }
}

override func layoutSubviews() {
    super.layoutSubviews()
    layoutHeaderView()
}

private func layoutHeaderView () {
    updateHeaderMargins()
    
    if let headerView = _elasticHeaderView {
        let headerSize = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
        let stretch = -min(0, contentOffset.y + headerSize.height)
        let headerRect = CGRect(
            x: 0,
            y: -headerSize.height - stretch,
            width: bounds.width,
            height: headerSize.height + stretch)
        
        headerView.frame = headerRect
        
        contentInset.top = headerSize.height - safeAreaInsets.top
    }
}

Here, we override layoutSubviews and attach an observer to the contentOffset parameter. This allows us to listen for changes to the scroll position, and the view layout without blocking the delegate outlet of the view, or requiring a viewController’s didLayout method. Then, the layoutHeaderView method does the majority of the work.

  1. First, we update the header margins. It’s possible that the safe area has changed since the last update, and it’s important to keep the margins current.
  2. Next, calculate the ideal size of the header using systemLayoutSizeFitting. This allows headers to be defined with Auto Layout constraints (which is super nice)
  3. Next, calculate the “stretch”. This is the distance the header must expand beyond its ideal height to account for the scroll view’s scroll position.
  4. Calculate a rect using all of these properties, pushing the header upward beyond the scroll view’s content. This allows the header to extend above the top of the scroll view. Assigning this new rect to the header view’s frame.
  5. Lastly, update the content inset of the scroll view to account for the possibility that a safe area has changed.

Now, we have a UITableView subclass which will automatically position a stretchy Auto Layout header whenever the view changes or is scrolled! The last thing to account for is storyboard compatibility! This is a nice easy fix, since we’ve already got a convenient method for it!

override func awakeFromNib() {
    super.awakeFromNib()
    
    let header = super.tableHeaderView
    super.tableHeaderView = nil
    _elasticHeaderView = header
}

When the view loads from a Nib or Storyboard, fetch the tableHeaderView from the superclass, set it to nil, and push the view into our internal header view field. This last piece of the puzzle lets our custom UITableView subclass play nicely with Storyboards. Dragging and dropping a new UIView to the top of an elastic table view will automatically assign the header, just like any old view!


And there you have it!

With just under 100 lines of Swift, you can create an entirely self-contained UITableView with an elastic header!

It’s a drop-in replacement for any table view in your project, plays nice with Auto Layout, can be configured and loaded from a Nib or Storyboard, and requires no special treatment on the part of your ViewController!