Reputation: 30408
This code bothers me. Below, I'm trying to find the first instance of a specific type of ViewController in a NavigationController's stack. Simple. But when I've found it, I have to then cast it to the type I just looked for, which seems redundant to me.
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.first(where: { $0 is T }) as? T else {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
Only thing I can think of is this...
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.flatMap({ $0 as? T }).first() else {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
...but I've repeatedly found using flatMap
like this tends to confuse people reading the code, and, as correctly pointed out in the comments below, iterates over the entire collection whereas first
doesn't do that.
So is there another way to solve this issue?
Upvotes: 1
Views: 64
Reputation: 154613
You can use case patterns to select the viewControllers of the type you are interested in and pop and return the first one you find:
extension UINavigationController {
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
for case let vc as T in viewControllers {
self.popToViewController(vc, animated: animated)
return vc
}
return nil
}
}
Example:
Use a button in OrangeViewController
to return to GreenViewController
earlier in the stack:
@IBAction func popToGreen(_ sender: UIButton) {
let greenVC = self.navigationController?.popToFirstViewController(
ofType: GreenViewController.self,
animated: true
)
// Modify a property in GreenViewController that
// will be moved into a label in viewWillAppear
greenVC?.labelText = "Returned here from Orange"
}
popToLastViewController(ofType:animated:)
You might also want a function to pop to the most recent viewController of a type. That is easily achieved with a simple modification (adding .reversed()
):
func popToLastViewController<T:UIViewController>(ofType type:T.Type, animated: Bool) -> T? {
for case let vc as T in viewControllers.reversed() {
self.popToViewController(vc, animated: animated)
return vc
}
return nil
}
Upvotes: 1
Reputation: 60130
I'm in favor of combining flatMap
and lazy
to get the behavior of conditionally casting to T
, stripping out mismatches, and not enumerating the whole array:
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.lazy.flatMap({ $0 as? T }).first {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
As for "confusing people that read the code:" flatMap
is fairly idiomatic Swift, and will be less ambiguous with the upcoming rename to compactMap
. If readers in your environment really have trouble, you could always write a small helper (generic or not) that performs the same work under a clearer name.
Upvotes: 1