Ashley Mills
Ashley Mills

Reputation: 53111

prepareForSegue switch with UINavigationController… a better way?

I have a couple of segues presenting modal view controllers.

class ProfileViewController: UIViewController {
    func configure(profile: Profile) { 
        // Some ProfileViewController specific config
    }
}

class SettingsViewController: UIViewController {
   func configure(settings: Settings) { 
        // Some SettingsViewController specific config
    }
}

So here's some pretty standard code we've all written many times…

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.destination {
    case let nav as UINavigationController:
        switch nav.topViewController {
        case let pvc as ProfileViewController:

            pvc.configure(profile: profile)
        case let svc as SettingsViewController:

            svc.configure(settings: settings)
        default:
            break
        }
    default:
        break
    }
}

I've also tried it this way…

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.destination {
    case let nav as UINavigationController
        where nav.topViewController is ProfileViewController:

        (nav.topViewController as! ProfileViewController).configure(profile: profile)
    case let nav as UINavigationController
        where nav.topViewController is SettingsViewController:

        (nav.topViewController as! SettingsViewController).configure(settings: settings)
    default:
        break
    }
}

But what I'd like to do is a combination of the two…

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.destination {
    case let nav as UINavigationController
        where let pvc = nav.topViewController as ProfileViewController:

        pvc.configure(profile: profile)
    case let nav as UINavigationController
        where let svc = nav.topViewController as SettingsViewController:

        svc.configure(settings: settings)
    default:
        break
    }
}

but obviously that won't compile. Has anyone come across a better pattern for this?


UPDATE

Expanding on @Serj's answer below…

extension UIStoryboardSegue {
    var destinationNavTopViewController: UIViewController? {
        return (destination as? UINavigationController)?.topViewController
    }
}

then

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch (segue.destination, destinationNavTopViewController) {
    case let (_, pvc as ProfileViewController):

        pvc.configure(profile: profile)
    case let (_, svc as SettingsViewController):

        svc.configure(settings: settings)
    case let (ovc as OtherViewController, _):
        ovc.configure…
    default:
        break
    }
}

Upvotes: 2

Views: 533

Answers (1)

Serj
Serj

Reputation: 684

When asking this question of How-to we can consider two cases:

  1. The specific use case on what you want to achieve
  2. The more general use-case of switch statements

Now for the specific use case it seems you only want to act on switching on the segue.destination when you have a navigation controller. You can easily achieve this with a if let or guard let like the following:

guard let navigationController = segue.destination as? UINavigationController else { return }
guard let topController = navigationController.topViewController else { return }
switch topController {
  case let profileViewController as ProfileViewController:
    print("Do something for profile view controller")
  default:
    break
}

Now the second case which is more general and about how to be able to have both variables available in the switch statement. The answer is tuples. For example I'm going to provide an example from the Apple's documentation and then provide an example for your specific use case.

let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}

Now for the example on how to achieve the above with your use-case be warned its an ugly example could be improved but gets the point across:

switch (segue.destination, (segue.destination as? UINavigationController)?.topViewController) {
case (let nav as UINavigationController, let viewController as ProfileViewController):
    return
// Maybe the destination wasn't a nav maybe it was the sign in view controller and you don't care about the second variable
case (let signInController as SignInViewController, let _):
    return
default:
    return
}

This is the power of switch statements being able to switch over Tuples so easily. I hope my example answers your question. I tried covering all parts of the question with all the ambiguities.

Upvotes: 3

Related Questions