Problem
I recently found a static C function on Stack Overflow which returns a UIViewController
reference from a containing subview using the responder chain:
static UIViewController *viewControllerForView(UIView *view) {
UIResponder *responder = view;
do {
responder = [responder nextResponder];
}
while (responder && ![responder isKindOfClass:[UIViewController class]]);
return (UIViewController *)responder;
}
I wanted to write this in Swift 3 as a programming exercise. My first pass looked like this:
func viewController(forView view: UIView) -> UIViewController? {
var responder = view as? UIResponder
while (responder != nil) {
if let viewController = responder as? UIViewController {
return viewController
}
responder = responder?.next
}
return nil
}
Then I tried using a repeat while
loop, to see if the structure would look any better:
func viewController(forView view: UIView) -> UIViewController? {
var responder: UIResponder? = view
repeat {
responder = responder?.next
if let vc = responder as? UIViewController {
return vc
}
} while responder != nil
return nil
}
I’m really not a fan of the while responder != nil
. Is there a way I can loop while checking if the responder is a UIViewController
? Maybe I can make this a recursive function by making the parameter a generic UIResponder
object.
Solution
Your code is correct and there is nothing wrong with checking if an optional is nil
unless you actually need the value if it is not.
Here is an example where the comparison to nil is inappropriate:
if someOptional != nil {
doSomethingWith(someOptional!)
}
This of course should be written as
if let something = someOptional {
doSomethingWith(something)
}
But in your case there is nothing wrong with the check.
A recursive version would also require the check for nil
, but it could be written using an if let
statement. But this version would have to take a responder as parameter:
func viewController(responder: UIResponder) -> UIViewController? {
if let vc = responder as? UIViewController {
return vc
}
if let next = responder.next {
return viewController(responder: next)
}
return nil
}
A more elegant way to express this would be a calculated property in an extension on UIResponder:
extension UIResponder {
var viewController: UIViewController? {
if let vc = self as? UIViewController {
return vc
}
return next?.viewController
}
}
Here the explicit check for nil can be eliminated thanks to optional chaining.
I would prefer your repeat/while solution though. But in the end it is a matter of taste.
The performance of the recursive version will be worse unless the compiler can do tail call elimination there. I am not sure if it will be able to do so. But this should hardly matter. If you get to a situation where this is a bottleneck you have worse problems.