簡體   English   中英

從 UIView 到 UIViewController?

[英]Get to UIViewController from UIView?

是否有從UIView到其UIViewController的內置方法? 我知道你可以通過[self view]UIViewController到它的UIView但我想知道是否有反向引用?

使用 Brock 發布的示例,我對其進行了修改,使其成為 UIView 而不是 UIViewController 的類別,並使其遞歸,以便任何子視圖都可以(希望)找到父 UIViewController。

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

@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
    // convenience function for casting and to "mask" the recursive function
    return (UIViewController *)[self traverseResponderChainForUIViewController];
}

- (id) traverseResponderChainForUIViewController {
    id nextResponder = [self nextResponder];
    if ([nextResponder isKindOfClass:[UIViewController class]]) {
        return nextResponder;
    } else if ([nextResponder isKindOfClass:[UIView class]]) {
        return [nextResponder traverseResponderChainForUIViewController];
    } else {
        return nil;
    }
}
@end

要使用此代碼,請將其添加到一個新的類文件中(我將其命名為“UIKitCategories”)並刪除類數據...將@interface 復制到標題中,將 @implementation 復制到 .m 文件中。 然后在您的項目中,#import "UIKitCategories.h" 並在 UIView 代碼中使用:

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

UIViewUIResponder的子類。 UIResponder展示了方法-nextResponder和一個返回nil的實現。 UIView覆蓋此方法,如UIResponder (出於某種原因而不是UIView ),如下所示:如果視圖具有視圖控制器,則由-nextResponder返回。 如果沒有視圖控制器,該方法將返回超級視圖。

將此添加到您的項目中,您就可以開始使用了。

@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

現在UIView有一個返回視圖控制器的工作方法。

由於長期以來這一直是公認的答案,我覺得我需要用更好的答案來糾正它。

關於需求的一些評論:

  • 您的視圖不需要直接訪問視圖控制器。
  • 視圖應該獨立於視圖控制器,並且能夠在不同的上下文中工作。
  • 如果您需要視圖以某種方式與視圖控制器交互,推薦的方式以及 Apple 在 Cocoa 中所做的就是使用委托模式。

如何實現它的示例如下:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

視圖與其委托進行交互(例如UITableView所做的),並且它並不關心它是在視圖控制器中還是在您最終使用的任何其他類中實現的。

我的原始答案如下:我不建議這樣做,其余的答案也不建議直接訪問視圖控制器

沒有內置的方法可以做到這一點。 雖然您可以通過在UIView上添加IBOutlet並在 Interface Builder 中連接它們來解決它,但不建議這樣做。 視圖不應該知道視圖控制器。 相反,您應該按照@Phil M 的建議進行操作,並創建一個用作委托的協議。

我建議使用更輕量級的方法來遍歷完整的響應者鏈,而無需在 UIView 上添加類別:

@implementation MyUIViewSubclass

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

@end

結合幾個已經給出的答案,我也將其與我的實現一起發布:

@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

該類別是我在我創建的每個應用程序中提供的啟用 ARC 的靜態庫的一部分。 它已經過多次測試,我沒有發現任何問題或泄漏。

PS:如果相關視圖是您的子類,則您不需要像我一樣使用類別。 在后一種情況下,只需將該方法放在您的子類中即可。

我修改回答,所以我可以傳遞任何視圖,按鈕,標簽等,以得到它的母公司UIViewController 這是我的代碼。

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

編輯 Swift 3 版本

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)!
    }

編輯 2:- Swift 擴展

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)!
    }
}

盡管這在技術上可以按照pgb 的建議解決,恕我直言,這是一個設計缺陷。 視圖不需要知道控制器。

不要忘記,您可以訪問視圖是其子視圖的窗口的根視圖控制器。 從那里,如果您使用導航視圖控制器並希望將新視圖推送到它上面:

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

但是,您首先需要正確設置窗口的 rootViewController 屬性。 當您第一次創建控制器時執行此操作,例如在您的應用程序委托中:

-(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];
}

我偶然發現了一種情況,我有一個要重用的PopoverController ,並在可重用視圖本身中添加了一些代碼(它實際上只不過是一個打開PopoverController的按鈕)。

雖然這在 iPad 上運行良好( UIPopoverController呈現自己,因此不需要引用UIViewController ),但讓相同的代碼工作意味着突然從你的UIViewController引用你的presentViewController 有點不一致吧?

如前所述,這不是在 UIView 中設置邏輯的最佳方法。 但是將所需的幾行代碼包裝在單獨的控制器中感覺真的沒用。

無論哪種方式,這是一個快速的解決方案,它為任何 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
    }
}

雖然這些答案在技術上是正確的,包括 Ushox,但我認為批准的方法是實施新協議或重用現有協議。 協議將觀察者與被觀察者隔離開來,有點像在它們之間放置一個郵槽。 實際上,這就是 Gabriel 通過 pushViewController 方法調用所做的; 視圖“知道”禮貌地要求您的 navigationController 推送視圖是正確的協議,因為 viewController 符合 navigationController 協議。 雖然您可以創建自己的協議,但僅使用 Gabriel 的示例並重新使用 UINavigationController 協議就可以了。

我不認為在某些情況下找出誰是視圖控制器是“壞”主意。 保存對這個控制器的引用可能是個壞主意,因為它可能會隨着超級視圖的變化而變化。 就我而言,我有一個遍歷響應者鏈的吸氣劑。

//。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;
}

用於查找 viewController 的最簡單的 do while 循環。

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

    do
    {
        nextResponder = [nextResponder nextResponder];

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

    } while (nextResponder != nil);

    return nil;
}

斯威夫特 4

(比其他答案更簡潔)

fileprivate extension UIView {

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

}

我的用例需要首先訪問視圖UIViewController :我有一個環繞AVPlayer / AVPlayerViewController的對象,我想提供一個簡單的show(in view: UIView)方法,它將嵌入AVPlayerViewControllerview 為此,我需要訪問viewUIViewController

這不會直接回答問題,而是對問題的意圖做出假設。

如果你有一個視圖並且在那個視圖中你需要調用另一個對象上的方法,比如視圖控制器,你可以使用 NSNotificationCenter 代替。

首先在頭文件中創建您的通知字符串

#define SLCopyStringNotification @"ShaoloCopyStringNotification"

在您看來,請調用 postNotificationName:

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

然后在您的視圖控制器中添加一個觀察者。 我在 viewDidLoad 中這樣做

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

現在(也在同一個視圖控制器中)實現你的方法 copyString: 如上面的@selector 所示。

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

我並不是說這是正確的方法,只是看起來比運行第一響應者鏈更干凈。 我使用此代碼在 UITableView 上實現 UIMenuController 並將事件傳遞回 UIViewController 以便我可以對數據執行某些操作。

這肯定是一個壞主意和錯誤的設計,但我相信我們都可以享受@Phil_M 提出的最佳答案的 Swift 解決方案:

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

如果您的意圖是做一些簡單的事情,例如顯示模態對話框或跟蹤數據,那並不能證明使用協議是合理的。 我個人將此函數存儲在一個實用程序對象中,您可以從任何實現 UIResponder 協議的對象中使用它:

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

所有功勞都歸功於@Phil_M

也許我來晚了。 但在這種情況下,我不喜歡類別(污染)。 我喜歡這種方式:

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

斯威夫特 4 版本

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
}

使用示例

 if let parent = self.view.parentViewController{

 }

更快捷的解決方案

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

swift 4 的更新版本:感謝@Phil_M 和@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)
}

Swift 5.2 的兩個解決方案:

  • 更多功能方面
  • 現在不需要return關鍵字了🤓

解決方案1:

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

解決方案2:

extension UIView {
    var parentViewController: UIViewController? {
        sequence(first: self) { $0.next }
            .compactMap{ $0 as? UIViewController }
            .first
    }
}
  • 此解決方案需要首先遍歷每個響應者,因此可能不是最高效的。

菲爾的回答:

在線: id nextResponder = [self nextResponder]; 如果 self(UIView) 不是 ViewController 視圖的子視圖,如果你知道 self(UIView) 的層次結構,你也可以使用: id nextResponder = [[self superview] nextResponder]; ...

另一種簡單的方法是擁有自己的視圖類並在視圖類中添加視圖控制器的屬性。 通常視圖控制器創建視圖,這是控制器可以將自己設置為屬性的地方。 基本上,它不是四處搜索(通過一些黑客攻擊)控制器,而是讓控制器將自己設置為視圖 - 這很簡單但很有意義,因為它是“控制”視圖的控制器。

我的解決方案可能會被認為有點虛假,但我遇到了與 mayoneez 類似的情況(我想切換視圖以響應 EAGLView 中的手勢),並且我以這種方式獲得了 EAGL 的視圖控制器:

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

如果您不打算將其上傳到 App Store,您也可以使用 UIView 的私有方法。

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

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

我認為存在被觀察者需要通知觀察者的情況。

我看到一個類似的問題,其中 UIViewController 中的 UIView 正在響應某種情況,它需要首先告訴其父視圖控制器隱藏后退按鈕,然后在完成后告訴父視圖控制器它需要將自己從堆棧中彈出。

我一直在與代表們一起嘗試,但沒有成功。

我不明白為什么這應該是一個壞主意?

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

要獲取給定視圖的控制器,可以使用 UIFirstResponder 鏈。

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

如果你的 rootViewController 是 UINavigationViewController,它是在 AppDelegate 類中設置的,那么

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

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

return nil;}

其中 c 需要視圖控制器類。

用法:

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

不可能。

我所做的是將 UIViewController 指針傳遞給 UIView (或適當的繼承)。 很抱歉,我無法用 IB 方法來解決這個問題,因為我不相信 IB。

回答第一個評論者:有時您確實需要知道誰給您打電話,因為它決定了您可以做什么。 例如,對於數據庫,您可能只有讀訪問權限或讀/寫...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM