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?
- Does this answer your question? Given a view, how do I get its viewController?Top-Master– Top-Master2021-09-07 19:15:54 +00:00Commented Sep 7, 2021 at 19:15
30 Answers
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]; 6 Comments
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.
1 Comment
UIView and the UIViewController. Phil M's answer featuring recursion is the way to go.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.
14 Comments
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 Comments
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.
1 Comment
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)! } } Comments
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.
3 Comments
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 } } Comments
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]; } 1 Comment
[[self navigationController] view] is the "main" (sub)view of the window, the rootViewController property of the window has to be set to navigationController which controls the "main" view immediately.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.
Comments
Two solutions as of Swift 5.2:
- More on the functional side
- No need for the
returnkeyword 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.
Comments
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; } Comments
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.
Comments
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; } Comments
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.
Comments
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
Comments
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; \ }) 1 Comment
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 } } 1 Comment
sequence bit). So if "swifty" means "more functional", then I guess it's more swifty.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{ } Comments
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) } Comments
var parentViewController: UIViewController? { let s = sequence(first: self) { $0.next } return s.compactMap { $0 as? UIViewController }.first } 1 Comment
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; 3 Comments
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];.ClassName *object - with an asterisk.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?
Comments
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.
Comments
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]]; Comments
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 ...