Get to UIViewController from UIView?

2018-12-31 18:51发布

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?

25条回答
不再属于我。
2楼-- · 2018-12-31 19:15

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.

查看更多
妖精总统
3楼-- · 2018-12-31 19:17

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]];
查看更多
回忆,回不去的记忆
4楼-- · 2018-12-31 19:18

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

查看更多
旧人旧事旧时光
5楼-- · 2018-12-31 19:19

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];
}
查看更多
零度萤火
6楼-- · 2018-12-31 19:19

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?

查看更多
与风俱净
7楼-- · 2018-12-31 19:20

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
    }
}
查看更多
登录 后发表回答