167

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

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
mahboudz
  • 39,196
  • 16
  • 97
  • 124
  • Basically, I am trying to call my viewController's viewWillAppear as my view is being dismissed. The view is being dismissed by the view itself which is detecting a tap and calling [self removeFromSuperview]; The viewController isn't calling viewWillAppear/WillDisappear/DidAppear/DidDisappear itself. – mahboudz Sep 03 '09 at 18:28
  • I meant I am trying to call viewWillDisappear as my view is being dismissed. – mahboudz Sep 12 '09 at 19:04
  • I think this thread has the answers: [Get to UIViewController from UIView on iPhone?](http://stackoverflow.com/questions/1340434/get-to-uiviewcontroller-from-uiview-on-iphone) – Ushox Sep 03 '09 at 12:53
  • 1
    Possible duplicate of [Get to UIViewController from UIView?](https://stackoverflow.com/questions/1340434/get-to-uiviewcontroller-from-uiview) – Efren Jun 07 '17 at 23:54

12 Answers12

325

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; \
})
Top-Master
  • 7,611
  • 5
  • 39
  • 71
mxcl
  • 26,392
  • 12
  • 99
  • 98
  • Just one thing: if you worry about category pollution, just define it as a static function rather than a macro. Also the brutal typecast is dangerous, and on top of that the macro may not be correct but am not sure. – mojuba Mar 05 '15 at 22:50
  • The macro works, I only use the macro version personally. I start a lot of projects and have a header I just drop in everywhere with a bunch of these macro utility functions in. Saves me time. If you don't like macros you can adapt it into a function, but static functions seem tedious, since you have to then put one in every file you want to use it. Seems like instead you'd want a non static function declared in a header and defined in a .m someplace? – mxcl Mar 06 '15 at 02:50
  • Nice to avoid some delegates or notifications. Thanks! – Ferran Maylinch Jun 01 '15 at 16:51
  • You should make it an extension of `UIResponder` ;). Very edifying post. – ScottyBlades Jan 11 '19 at 05:58
56

@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
Federico Zanetello
  • 3,321
  • 1
  • 25
  • 22
  • `parentViewController` can not be defined `public` if the extension is in the same file with your `UIView` you can set it `fileprivate`, it compiles but it doesnt work! –  Mar 06 '19 at 20:52
  • It is working fine. I have used this for back button action inside common header .xib file. – McDonal_11 May 15 '20 at 08:27
  • In Xamarin: public static class Extensions { public static UIViewController ParentViewController(this UIResponder view) => (view.NextResponder as UIViewController) ?? view.NextResponder?.ParentViewController(); } – Softlion Sep 01 '20 at 04:31
44

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.

Wayne
  • 59,728
  • 15
  • 131
  • 126
Dimitar Dimitrov
  • 16,032
  • 5
  • 53
  • 55
  • 27
    I'm not sure if it would break MVC principles. At any one point, a view has only one view controller. Being able to get to it in order to pass a message back to it, should be an automatic feature, not one where you have to work to achieve (by adding a property to keep track). One could say the same thing about views: why do you need to know who child you are? Or whether there are other sibling views. Yet there are ways to get those objects. – mahboudz Sep 29 '09 at 07:42
  • You are somewhat right about the view, knowing about its parent, it's not super-clear design decision, but it's already established to do some actions, using directly a superview member variable (check parent type, remove from parent, etc). Having worked with PureMVC recently, I have become a little more nit-picky about design abstraction :) I would make parallel between iPhone's UIView and UIViewController classes and PureMVC's View and Mediator classes - most of the time, the View class doesn't need to know about its MVC handler/interface (UIViewController/Mediator). – Dimitar Dimitrov Sep 29 '09 at 08:41
25

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.
Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • This is exactly why I came to this question. Apparently, 'nextResponder' does the same thing, but I appreciate the insight that this answer provides. I understand and like MVC, but debugging is a different animal! – mbm29414 Aug 07 '14 at 17:03
  • 4
    It appears this only works on the view controller's main view, not on any subviews of it. `_viewControllerForAncestor` will traverse up the superviews until it finds the first one that belongs to a view controller. – n00neimp0rtant Jan 15 '15 at 20:55
  • Thanks @n00neimp0rtant ! I'm upvoting this answer so people see your comment. – eyuelt Jul 13 '15 at 17:12
  • Update answer with more methods. – Léo Natan Jul 13 '15 at 18:04
  • 1
    This is helpful when debugging. – evanchin Jul 29 '15 at 04:37
10

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
Komal12
  • 3,340
  • 4
  • 16
  • 25
Andrey
  • 489
  • 6
  • 14
7

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)
}
iTSangar
  • 462
  • 8
  • 10
3

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.

m4js7er
  • 119
  • 1
  • 3
1

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

Community
  • 1
  • 1
Nava Carmon
  • 4,523
  • 3
  • 40
  • 74
  • If you have a handful of views, and one closes all the views, and you need to call viewWillDisappear, wouldn't it be easier for that view to detect the tap than to hand the tap to the view controller and have the view controller check with all the views to see which one was tapped on? – mahboudz Sep 29 '09 at 07:37
1

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
    }
}
Denis Krivitski
  • 654
  • 8
  • 11
0

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.

0

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
  }                       
}
Hackbarth
  • 19
  • 2
-1

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 :)

Scott McCoy
  • 308
  • 2
  • 13