Categories
ibsegueaction ios swift uistoryboard

Using @IBSegueAction with UINavigationController

Thanks to AD Progress’s excellent research and answer below, I finally figured out what’s going on with IBSegueActions.

If you hook up a rootViewController segue (or any other kind, presumably) to an IBSegueAction in its UINavigationContoller, but it doesn’t find it there, it will look at the next view controller up the responder chain, until it finds an IBSegueAction with the correct signature. If it doesn’t find one, it’ll call init(with coder: NSCoder) on the rootViewController. If only that was documented somewhere…


Notes

Note: This is a minimal example to illustrate the problem – how to pass parameters when initialising a view controller contained in a modal UINavigationController using an IBSegueAction. I’m aware that this isn’t how you would normally present a detail view.

Note 2: I know how to use segues with prepare(for segue: UIStoryboardSegue). What I want to know how is how to pass non-optional parameters when initialising a view controller contained in a modal UINavigationController using an IBSegueAction

Note 3: I know how to use use an IBSegueAction to a view controller not contained in a UINavigationController (e.g. pushing on the nav stack)


Original Question

I have the following setup…

enter image description here

A list of people in a UITableViewController. Tapping on a PersonCell presents a PersonViewController inside a UINavigationController

To avoid having an optional Person in the PersonViewController, I’m trying to use IBSegueActions.

My first thought was that I need one for the segue between the PeopleViewController and the PersonNavController, and the segue between the PersonNavController and the PersonViewController as follows…

class PeopleViewController: UITableViewController {
// Table view delegate methods here
@IBSegueAction func showPerson(_ coder: NSCoder, sender: Any?) -> PersonNavController? {
guard let cell = sender as? PersonCell else { return nil }
return PersonNavController(coder: coder, person: cell.person)
}
}


class PersonNavController: UINavigationController {
private let person: Person
required init?(coder: NSCoder, person: Person) {
self.person = person
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@IBSegueAction func root(_ coder: NSCoder) -> PersonViewController? {
return PersonViewController(coder: coder, person: person)
}
}


class PersonViewController: UIViewController {
@IBOutlet private weak var name: UILabel!
private let person: Person
required init?(coder: NSCoder, person: Person) {
self.person = person
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
name.text = person.firstName
}
}

The problem here, it turns out, is that even though you can hook up a segue action to the segue between a UINavigationController and its rootViewController, it doesn’t get fired as, in the example above, the super.init(coder: coder) in PersonNavController calls PersonViewController.init(coder: NSCoder) (which isn’t implemented).

Any thoughts as to how I can get this working?