1
\$\begingroup\$

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.

asked Oct 6, 2016 at 16:17
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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.

answered Oct 7, 2016 at 21:37
\$\endgroup\$
1
  • \$\begingroup\$ You can make your recursive solution even more elegant using the nil-coalescing operator: return self as? UIViewController ?? next?.viewController \$\endgroup\$ Commented Oct 8, 2016 at 14:53

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.