Given a view, how do I get its viewController?

IosIphoneDebuggingUiviewUiviewcontroller

Ios Problem Overview


I have a pointer to a UIView. How do I access its UIViewController? [self superview] is another UIView, but not the UIViewController, right?

Ios Solutions


Solution 1 - Ios

From the UIResponder documentation for nextResponder:

> The UIResponder class does not store or set the next responder automatically, instead returning nil by default. Subclasses must override this method to set the next responder. UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t); UIViewController implements the method by returning its view’s superview; UIWindow returns the application object, and UIApplication returns nil.

So, if you recurse a view’s nextResponder until it is of type UIViewController, then you have any view’s parent viewController.

Note that it still may not have a parent view controller. But only if the view has not part of a viewController’s view’s view hierarchy.

Swift 3 and Swift 4.1 extension:

extension UIView {
    var parentViewController: UIViewController? {
        // Starts from next (As we know self is not a UIViewController).
        var parentResponder: UIResponder? = self.next
        while parentResponder != nil {
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
            parentResponder = parentResponder?.next
        }
        return nil
    }
}

Swift 2 extension:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self.nextResponder()
        while parentResponder != nil {
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
            parentResponder = parentResponder!.nextResponder()
        }
        return nil
    }
}

Objective-C category:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = [self nextResponder];
    while (responder != nil) {
        if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)responder;
        }
        responder = [responder nextResponder];
    }
    return nil;
}
@end

This macro avoids category pollution:

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})

Solution 2 - Ios

@andrey answer in one line (tested in Swift 4.1):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

usage:

 let vc: UIViewController = view.parentViewController

Solution 3 - Ios

Yes, the superview is the view that contains your view. Your view shouldn't know which exactly is its view controller, because that would break MVC principles.

The controller, on the other hand, knows which view it's responsible for (self.view = myView), and usually, this view delegates methods/events for handling to the controller.

Typically, instead of a pointer to your view, you should have a pointer to your controller, which in turn can either execute some controlling logic, or pass something to its view.

Solution 4 - Ios

For debug purposes only, you can call _viewDelegate on views to get their view controllers. This is private API, so not safe for App Store, but for debugging it is useful.

Other useful methods:

  • _viewControllerForAncestor - get the first controller that manages a view in the superview chain. (thanks n00neimp0rtant)
  • _rootAncestorViewController - get the ancestor controller whose view hierarchy is set in the window currently.

Solution 5 - Ios

To get reference to UIViewController having UIView, you could make extension of UIResponder (which is super class for UIView and UIViewController), which allows to go up through the responder chain and thus reaching UIViewController (otherwise returning nil).

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc

Solution 6 - Ios

The fast and generic way in Swift 3:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}

Solution 7 - Ios

If you are not familiar with the code and you want to find ViewController coresponding to given view, then you can try:

  1. Run app in debug
  2. Navigate to screen
  3. Start View inspector
  4. Grab the View you want to find (or a child view even better)
  5. From the right pane get the address (e.g. 0x7fe523bd3000)
  6. In debug console start writing commands:

po (UIView *)0x7fe523bd3000
po [(UIView *)0x7fe523bd3000 nextResponder]
po [[(UIView *)0x7fe523bd3000 nextResponder] nextResponder]
po [[[(UIView *)0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
...

In most cases you will get UIView, but from time to time there will be UIViewController based class.

Solution 8 - Ios

I think you can propagate the tap to the view controller and let it handle it. This is more acceptable approach. As for accessing a view controller from its view, you should maintain a reference to a view controller, since there is no another way. See this thread, it might help: Accessing view controller from a view

Solution 9 - Ios

More type safe code for Swift 3.0

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}

Solution 10 - Ios

Alas, this is impossible unless you subclass the view and provide it with an instance property or alike which stores the view controller reference inside it once the view is added to the scene...

In most cases - it is very easy to work around the original problem of this post as most view controllers are well-known entities to the programmer who was responsible for adding any subviews to the ViewController's View ;-) Which is why I guess that Apple never bothered to add that property.

Solution 11 - Ios

A little bit late, but here's an extension that enable you to find a responder of any type, including ViewController.

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder
    
    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }
    
    return resp
  }                       
}

Solution 12 - Ios

If you set a breakpoint, you can paste this into the debugger to print the view hierarchy:

po [[UIWindow keyWindow] recursiveDescription]

You should be able to find your view's parent somewhere in that mess :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmahboudzView Question on Stackoverflow
Solution 1 - IosmxclView Answer on Stackoverflow
Solution 2 - IosFederico ZanetelloView Answer on Stackoverflow
Solution 3 - IosDimitar DimitrovView Answer on Stackoverflow
Solution 4 - IosLéo NatanView Answer on Stackoverflow
Solution 5 - IosAndreyView Answer on Stackoverflow
Solution 6 - IosiTSangarView Answer on Stackoverflow
Solution 7 - Iosm4js7erView Answer on Stackoverflow
Solution 8 - IosNava CarmonView Answer on Stackoverflow
Solution 9 - IosDenis KrivitskiView Answer on Stackoverflow
Solution 10 - IosMorten CarlsenView Answer on Stackoverflow
Solution 11 - IosHackbarthView Answer on Stackoverflow
Solution 12 - IosScott McCoyView Answer on Stackoverflow