Accessing a UIView’s parent UIViewController using the UIResponder chain

Posted on

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.

Leave a Reply

Your email address will not be published. Required fields are marked *