Better Segue Identifiers

March 14, 2016

Lately I’ve been thinking about segue identifiers. We all know it’s bad practice to use strings instead of constants for identifiers. All it takes is one spelling mistake or forgetting to update a name, and you have big trouble that’s not visible until runtime. But there’s no getting around this in Xcode, unless you stop using segues altogether (and no, you don’t want to do that).

In my current project (the first where I’ve gone all-in on storyboards and segues) I’ve reached the stage where many view controllers have a non-trivial number of segues. And so I started wondering if maybe there is some sort of safer, Swift-y way of managing identifiers in code. Of course it turns out that @NatashaTheRobot has already thought about this! You can read her post about it right here.

I tweaked her solution a little bit for my own needs. The biggest change is adding segueIdentifierForName(), to check the segue identifier string given in shouldPerformSegueWithIdentifier(). Natasha’s blog has the background, but to start you create a SegueHandlerType protocol:

import UIKit

protocol SegueHandler {
    typealias SegueIdentifier: RawRepresentable
}

extension SegueHandler where Self: UIViewController, SegueIdentifier.RawValue == String {
    
    func performSegueWithIdentifier(segueIdentifier: SegueIdentifier, sender: AnyObject?) {
        performSegueWithIdentifier(segueIdentifier.rawValue, sender: sender)
    }
    
    func segueIdentifierForName(name: String) -> SegueIdentifier {
        guard let identifier = SegueIdentifier(rawValue: name) else { fatalError("Invalid segue `\(name)`.") }
        return identifier
    }
    
    func segueIdentifierForSegue(segue: UIStoryboardSegue) -> SegueIdentifier {
        guard let name = segue.identifier else { fatalError("Segue has empty identifier!") }
        return segueIdentifierForName(name)
    }
}

When you create a segue, make the source view controller implement SegueHandlerType by providing a SegueIdentifier enum with the identifiers you set in Interface Builder. You don’t have to include the segues you’re not dealing with in code, but you should— it’s useful when making changes to see all the segues declared in code.

class LibraryViewController: UITableViewController, SegueHandler {
        
    enum SegueIdentifier: String {
        case OpenFile
        case ChooseFolder
        case OpenSettings
    }
        
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        switch segueIdentifierForSegue(segue) {
        case .OpenFile:
            // prep the destination view controller
        case .ChooseFolder:
            // prep the destination view controller
        case .OpenSettings:
            // prep the destination view controller
        }
    }
        
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        performSegueWithIdentifier(.OpenTab, sender: nil)
    }    
}

I’ll reiterate: this is ALL @NatashaTheRobot’s solution, just changed a little for my own needs. I can’t take credit for her awesome work!

It’s not 100% foolproof, but I like this way of dealing with segue identifiers. I’m using it in a couple of my projects, and as long as you’re careful to keep your code current with Interface Builder it’s a much cleaner and safer way to work.

Marc Charbonneau is a mobile software engineer in Portland, OR. Want to reply to this article? Get in touch on Twitter @mbcharbonneau.