Get to UIViewController from UIView?

IosObjective CCocoa TouchUiviewUiviewcontroller

Ios Problem Overview


Is there a built-in way to get from a UIView to its UIViewController? I know you can get from UIViewController to its UIView via [self view] but I was wondering if there is a reverse reference?

Ios Solutions


Solution 1 - Ios

Using the example posted by Brock, I modified it so that it is a category of UIView instead UIViewController and made it recursive so that any subview can (hopefully) find the parent UIViewController.

@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
@end

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

@end

To use this code, add it into an new class file (I named mine "UIKitCategories") and remove the class data... copy the @interface into the header, and the @implementation into the .m file. Then in your project, #import "UIKitCategories.h" and use within the UIView code:

// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];

Solution 2 - Ios

UIView is a subclass of UIResponder. UIResponder lays out the method -nextResponder with an implementation that returns nil. UIView overrides this method, as documented in UIResponder (for some reason instead of in UIView) as follows: if the view has a view controller, it is returned by -nextResponder. If there is no view controller, the method will return the superview.

Add this to your project and you're ready to roll.

@interface UIView (APIFix)
- (UIViewController *)viewController;
@end

@implementation UIView (APIFix)

- (UIViewController *)viewController {
    if ([self.nextResponder isKindOfClass:UIViewController.class])
        return (UIViewController *)self.nextResponder;
    else
        return nil;
}
@end

Now UIView has a working method for returning the view controller.

Solution 3 - Ios

Since this has been the accepted answer for a long time, I feel I need to rectify it with a better answer.

Some comments on the need:

  • Your view should not need to access the view controller directly.
  • The view should instead be independent of the view controller, and be able to work in different contexts.
  • Should you need the view to interface in a way with the view controller, the recommended way, and what Apple does across Cocoa is to use the delegate pattern.

An example of how to implement it follows:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

The view interfaces with its delegate (as UITableView does, for instance) and it doesn't care if its implemented in the view controller or in any other class that you end up using.

My original answer follows: I don't recommend this, neither the rest of the answers where direct access to the view controller is achieved

There is no built-in way to do it. While you can get around it by adding a IBOutlet on the UIView and connecting these in Interface Builder, this is not recommended. The view should not know about the view controller. Instead, you should do as @Phil M suggests and create a protocol to be used as the delegate.

Solution 4 - Ios

I would suggest a more lightweight approach for traversing the complete responder chain without having to add a category on UIView:

@implementation MyUIViewSubclass

- (UIViewController *)viewController {
    UIResponder *responder = self;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

@end

Solution 5 - Ios

Combining several already given answers, I'm shipping on it as well with my implementation:

@implementation UIView (AppNameAdditions)

- (UIViewController *)appName_viewController {
    /// Finds the view's view controller.
    
    // Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
    Class vcc = [UIViewController class];
    
    // Traverse responder chain. Return first found view controller, which will be the view's view controller.
    UIResponder *responder = self;
    while ((responder = [responder nextResponder]))
        if ([responder isKindOfClass: vcc])
            return (UIViewController *)responder;
    
    // If the view controller isn't found, return nil.
    return nil;
}

@end

The category is part of my ARC-enabled static library that I ship on every application I create. It's been tested several times and I didn't find any problems or leaks.

P.S.: You don't need to use a category like I did if the concerned view is a subclass of yours. In the latter case, just put the method in your subclass and you're good to go.

Solution 6 - Ios

I modified de answer so I can pass any view, button, label etc. to get it's parent UIViewController. Here is my code.

+(UIViewController *)viewController:(id)view {
    UIResponder *responder = view;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

Edit Swift 3 Version

class func viewController(_ view: UIView) -> UIViewController {
        var responder: UIResponder? = view
        while !(responder is UIViewController) {
            responder = responder?.next
            if nil == responder {
                break
            }
        }
        return (responder as? UIViewController)!
    }

Edit 2:- Swift Extention

extension UIView
{
    //Get Parent View Controller from any view
    func parentViewController() -> UIViewController {
        var responder: UIResponder? = self
        while !(responder is UIViewController) {
            responder = responder?.next
            if nil == responder {
                break
            }
        }
        return (responder as? UIViewController)!
    }
}

Solution 7 - Ios

Even though this can technically be solved as pgb recommends, IMHO, this is a design flaw. The view should not need to be aware of the controller.

Solution 8 - Ios

Don't forget that you can get access to the root view controller for the window that the view is a subview of. From there, if you are e.g. using a navigation view controller and want to push a new view onto it:

    [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];

You will need to set up the rootViewController property of the window properly first, however. Do this when you first create the controller e.g. in your app delegate:

-(void) applicationDidFinishLaunching:(UIApplication *)application {
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    RootViewController *controller = [[YourRootViewController] alloc] init];
    [window setRootViewController: controller];
    navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
    [controller release];
    [window addSubview:[[self navigationController] view]];
    [window makeKeyAndVisible];
}

Solution 9 - Ios

I stumbled upon a situation where I have a small component I want to reuse, and added some code in a reusable view itself(it's really not much more than a button that opens a PopoverController).

While this works fine in the iPad (the UIPopoverController presents itself, therefor needs no reference to a UIViewController), getting the same code to work means suddenly referencing your presentViewController from your UIViewController. Kinda inconsistent right?

Like mentioned before, it's not the best approach to have logic in your UIView. But it felt really useless to wrap the few lines of code needed in a separate controller.

Either way, here's a swift solution, which adds a new property to any UIView:

extension UIView {

	var viewController: UIViewController? {

		var responder: UIResponder? = self

		while responder != nil {

			if let responder = responder as? UIViewController {
				return responder
			}
			responder = responder?.nextResponder()
		}
		return nil
	}
}

Solution 10 - Ios

While these answers are technically correct, including Ushox, I think the approved way is to implement a new protocol or re-use an existing one. A protocol insulates the observer from the observed, sort of like putting a mail slot in between them. In effect, that is what Gabriel does via the pushViewController method invocation; the view "knows" that it is proper protocol to politely ask your navigationController to push a view, since the viewController conforms to the navigationController protocol. While you can create your own protocol, just using Gabriel's example and re-using the UINavigationController protocol is just fine.

Solution 11 - Ios

I don't think it's "bad" idea to find out who is the view controller for some cases. What could be a bad idea is to save the reference to this controller as it could change just as superviews change. In my case I have a getter that traverses the responder chain.

//.h

@property (nonatomic, readonly) UIViewController * viewController;

//.m

- (UIViewController *)viewController
{
    for (UIResponder * nextResponder = self.nextResponder;
         nextResponder;
         nextResponder = nextResponder.nextResponder)
    {
        if ([nextResponder isKindOfClass:[UIViewController class]])
            return (UIViewController *)nextResponder;
    }
    
    // Not found
    NSLog(@"%@ doesn't seem to have a viewController". self);
    return nil;
}

Solution 12 - Ios

The simplest do while loop for finding the viewController.

-(UIViewController*)viewController
{
    UIResponder *nextResponder =  self;

    do
    {
        nextResponder = [nextResponder nextResponder];

        if ([nextResponder isKindOfClass:[UIViewController class]])
            return (UIViewController*)nextResponder;

    } while (nextResponder != nil);

    return nil;
}

Solution 13 - Ios

Swift 4

(more concise than the other answers)

fileprivate extension UIView {
  
  var firstViewController: UIViewController? {
    let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
    return firstViewController as? UIViewController
  }
  
}

My use case for which I need to access the view first UIViewController: I have an object that wraps around AVPlayer / AVPlayerViewController and I want to provide a simple show(in view: UIView) method that will embed AVPlayerViewController into view. For that, I need to access view's UIViewController.

Solution 14 - Ios

This doesn't answer the question directly, but rather makes an assumption about the intent of the question.

If you have a view and in that view you need to call a method on another object, like say the view controller, you can use the NSNotificationCenter instead.

First create your notification string in a header file

#define SLCopyStringNotification @"ShaoloCopyStringNotification"

In your view call postNotificationName:

- (IBAction) copyString:(id)sender
{
    [[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}

Then in your view controller you add an observer. I do this in viewDidLoad

- (void)viewDidLoad
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(copyString:)
                                                 name:SLCopyStringNotification
                                               object:nil];
}

Now (also in the same view controller) implement your method copyString: as depicted in the @selector above.

- (IBAction) copyString:(id)sender
{
	CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
	UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
	[gpBoard setString:result.stringResult];
}

I'm not saying this is the right way to do this, it just seems cleaner than running up the first responder chain. I used this code to implement a UIMenuController on a UITableView and pass the event back up to the UIViewController so I can do something with the data.

Solution 15 - Ios

It's surely a bad idea and a wrong design, but I'm sure we can all enjoy a Swift solution of the best answer proposed by @Phil_M:

static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
    func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
        if let nextResponder = responder.nextResponder() {
            if let nextResp = nextResponder as? UIViewController {
                return nextResp
            } else {
                return traverseResponderChainForUIViewController(nextResponder)
            }
        }
        return nil
    }

    return traverseResponderChainForUIViewController(responder)
}

If your intention is to do simple things, as showing a modal dialog or tracking data, that doesn't justify the use of a protocol. I personally store this function in an utility object, you can use it from anything that implement the UIResponder protocol as:

if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}

All credit to @Phil_M

Solution 16 - Ios

Maybe I'm late here. But in this situation I don't like category (pollution). I love this way:

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

Solution 17 - Ios

Swift 4 version

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

Usage example

 if let parent = self.view.parentViewController{
        
 }

Solution 18 - Ios

Swiftier solution

extension UIView {
    var parentViewController: UIViewController? {
        for responder in sequence(first: self, next: { $0.next }) {
            if let viewController = responder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Solution 19 - Ios

Updated version for swift 4 : Thanks for @Phil_M and @paul-slm

static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
    func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
        if let nextResponder = responder.next {
            if let nextResp = nextResponder as? UIViewController {
                return nextResp
            } else {
                return traverseResponderChainForUIViewController(responder: nextResponder)
            }
        }
        return nil
    }
    
    return traverseResponderChainForUIViewController(responder: responder)
}

Solution 20 - Ios

Two solutions as of Swift 5.2:

  • More on the functional side
  • No need for the return keyword now 邏

Solution 1:

extension UIView {
    var parentViewController: UIViewController? {
        sequence(first: self) { $0.next }
            .first(where: { $0 is UIViewController })
            .flatMap { $0 as? UIViewController }
    }
}

Solution 2:

extension UIView {
    var parentViewController: UIViewController? {
        sequence(first: self) { $0.next }
            .compactMap{ $0 as? UIViewController }
            .first
    }
}
  • This solution requires iterating through each responder first, so may not be the most performant.

Solution 21 - Ios

To Phil's answer:

In line: id nextResponder = [self nextResponder]; if self(UIView) is not a subview of ViewController's view, if you know hierarchy of self(UIView) you can use also: id nextResponder = [[self superview] nextResponder];...

Solution 22 - Ios

My solution would probably be considered kind of bogus but I had a similar situation as mayoneez (I wanted to switch views in response to a gesture in an EAGLView), and I got the EAGL's view controller this way:

EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;

Solution 23 - Ios

I think there is a case when the observed needs to inform the observer.

I see a similar problem where the UIView in a UIViewController is responding to a situation and it needs to first tell its parent view controller to hide the back button and then upon completion tell the parent view controller that it needs to pop itself off the stack.

I have been trying this with delegates with no success.

I don't understand why this should be a bad idea?

Solution 24 - Ios

Another easy way is to have your own view class and add a property of the view controller in the view class. Usually the view controller creates the view and that is where the controller can set itself to the property. Basically it is instead of searching around (with a bit of hacking) for the controller, having the controller to set itself to the view - this is simple but makes sense because it is the controller that "controls" the view.

Solution 25 - Ios

If you aren't going to upload this to the App Store, you can also use a private method of UIView.

@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end

// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];

Solution 26 - Ios

var parentViewController: UIViewController? {
    let s = sequence(first: self) { $0.next }
    return s.compactMap { $0 as? UIViewController }.first
}

Solution 27 - Ios

To get the controller of a given view, one can use UIFirstResponder chain.

customView.target(forAction: Selector("viewDidLoad"), withSender: nil)

Solution 28 - Ios

If your rootViewController is UINavigationViewController, which was set up in AppDelegate class, then

    + (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];

for (UIViewController *v in arrVc)
{
    if ([v isKindOfClass:c])
    {
        return v;
    }
}

return nil;}

Where c required view controllers class.

USAGE:

     RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];

Solution 29 - Ios

There is no way.

What I do is pass the UIViewController pointer to the UIView (or an appropriate inheritance). I'm sorry I can't help with the IB approach to the problem because I don't believe in IB.

To answer the first commenter: sometimes you do need to know who called you because it determines what you can do. For example with a database you might have read access only or read/write ...

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
QuestionbertrandomView Question on Stackoverflow
Solution 1 - IosPhil MView Answer on Stackoverflow
Solution 2 - IosBrockView Answer on Stackoverflow
Solution 3 - IospgbView Answer on Stackoverflow
Solution 4 - Iosde.View Answer on Stackoverflow
Solution 5 - IosConstantino TsarouhasView Answer on Stackoverflow
Solution 6 - IosVarun NahariaView Answer on Stackoverflow
Solution 7 - IosUshoxView Answer on Stackoverflow
Solution 8 - IosGabrielView Answer on Stackoverflow
Solution 9 - IosKevin RView Answer on Stackoverflow
Solution 10 - IosPapaSmurfView Answer on Stackoverflow
Solution 11 - IosRiveraView Answer on Stackoverflow
Solution 12 - IosMohd Iftekhar QurashiView Answer on Stackoverflow
Solution 13 - IosfrouoView Answer on Stackoverflow
Solution 14 - IosShaoloView Answer on Stackoverflow
Solution 15 - IosPaul SlmView Answer on Stackoverflow
Solution 16 - IosleeView Answer on Stackoverflow
Solution 17 - Ioslevin vargheseView Answer on Stackoverflow
Solution 18 - IosIgor PalagutaView Answer on Stackoverflow
Solution 19 - IosdicleView Answer on Stackoverflow
Solution 20 - Iosjason zView Answer on Stackoverflow
Solution 21 - IossagView Answer on Stackoverflow
Solution 22 - IosgulchriderView Answer on Stackoverflow
Solution 23 - Iosuser7865437View Answer on Stackoverflow
Solution 24 - IosjackView Answer on Stackoverflow
Solution 25 - Iosuser7085621View Answer on Stackoverflow
Solution 26 - IosklaudasView Answer on Stackoverflow
Solution 27 - IosBlazej SLEBODAView Answer on Stackoverflow
Solution 28 - IosAnsari AwaisView Answer on Stackoverflow
Solution 29 - IosJohn SmithView Answer on Stackoverflow