簡體   English   中英

如何從彈出窗口中顯示UIViewController中找到UIPopoverController?

[英]How can I find the UIPopoverController from the UIViewController being displayed in a popover?

使用UIViewController的實例,有什么辦法可以找到用於呈現它的UIPopoverController嗎? 我還想找到首先顯示UIPopoverController的UIViewController。

我通常會使用委托或其他類型的通知從顯示的視圖控制器向顯示的視圖控制器發送信號,但在這種情況下,我正在嘗試創建一個可重復使用的自定義segue,解除彈出窗口然后轉到另一個視圖在主視圖中。

你會認為這很簡單( UIViewController甚至有一個私有的_popoverController屬性!),但事實並非如此。

一般的答案是,你必須保存到一個參考UIPopoverControllerUIViewController ,它被呈現,在時間UIViewController創建。

  1. 如果您以編程方式創建UIPopoverController ,那么就是將引用存儲在UIViewController子類中的時間。

  2. 如果您使用的是Storyboard和Segues,則可以在prepareForSegue方法中將UIPopoverController從segue中取出:

     UIPopoverController* popover = [(UIStoryboardPopoverSegue*)segue popoverController]; 

當然,請確保您的segue真的是一個UIStoryboardPopoverSegue!

我的建議是利用您自己的自定義屬性和UIKit中的私有API的組合。 為了避免應用商店拒絕,任何私有API都應該針對發布版本進行編譯,並且應該僅用於檢查您的實現。

首先,讓我們將自定義屬性構建到UIViewController上的類別中。 這允許實現中的一些特權,並且它不需要您返回並從某個自定義視圖控制器子類派生每個類。

// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

@property (assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;

@end

現在進行實現 - 我們將使用Objective C運行時的關聯對象API為此屬性提供存儲。 請注意,選擇器是用於存儲對象的唯一鍵的不錯選擇,因為它自動由編譯器單獨使用,並且極不可能被任何其他客戶端用於此目的。

// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@implementation UIViewController (isPresentedInPopover)

- (void)setPresentedInPopover:(BOOL)presentedInPopover
{
    objc_setAssociatedObject(self,
                             @selector(isPresentedInPopover),
                             [NSNumber numberWithBool:presentedInPopover],
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isPresentedInPopover
{
    NSNumber *wrappedBool = objc_getAssociatedObject(self, @selector(isPresentedInPopover));
    BOOL userValue = [wrappedBool boolValue];
    return userValue ?: [[self parentViewController] isPresentedInPopover];
}

@end

因此,使用它作為類別有一個方便的副作用 - 您可以調用parentViewController並查看它是否也包含在彈出窗口中。 這樣你就可以在UINavigationController上設置屬性,它的所有子視圖控制器都會正確響應isPresentedInPopover 要使用子類完成此操作,您要么嘗試在每個新的子視圖控制器上設置它,要么在子類化導航控制器或其他可怕的東西。

更多運行時魔術

Objective C Runtime還有更多內容可以解決這個特定問題,我們可以使用它們跳轉到Apple的私有實現細節,並根據它檢查自己的應用程序。 對於發布版本,這個額外的代碼將編譯出來,所以在提交到商店時無需擔心 Sauron Apple的全 視角

你可以從UIViewController.h看到有一個ivar定義為帶有@package范圍的UIPopoverController* _popoverController 幸運的是,這僅由編譯器強制執行。 就運行時而言, 沒有什么是神聖的,並且從任何地方訪問這些ivar都非常容易。 我們將在每次訪問屬性時添加一個僅調試運行時檢查,以確保我們保持一致。

// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@implementation UIViewController (isPresentedInPopover)

- (void)setPresentedInPopover:(BOOL)presentedInPopover
{
    objc_setAssociatedObject(self,
                             @selector(isPresentedInPopover),
                             [NSNumber numberWithBool:presentedInPopover],
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isPresentedInPopover
{
    NSNumber *wrappedBool = objc_getAssociatedObject(self, @selector(isPresentedInPopover));
    BOOL userValue = [wrappedBool boolValue];

#if DEBUG
    Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController");
    UIPopoverController *popover = object_getIvar(self, privatePopoverIvar);
    BOOL privateAPIValue = popover != nil;

    if (userValue != privateAPIValue) {
        [NSException raise:NSInternalInconsistencyException format:
         @"-[%@ %@] "
         "returning %@ "
         "while private UIViewController API suggests %@. "
         "Did you forget to set 'presentedInPopover'?",
         NSStringFromClass([self class]), NSStringFromSelector(_cmd),
         userValue ? @"YES" : @"NO",
         privateAPIValue ? @"YES" : @"NO"];
    }
#endif

    return userValue ?: [[self parentViewController] isPresentedInPopover];
}

@end

錯誤地使用該屬性時,您將在控制台上收到如下消息:

2012-09-18 14:28:30.375 MyApp[41551:c07] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Consistency error in -[UINavigationController isPresentedInPopover]: returning NO while private UIViewController API suggests YES. Did you forget to set 'presentedInPopover'?'

...但是當關閉DEBUG標志或設置為0時進行編譯時,它會編譯為與之前完全相同的代碼。

為自由和蠻干

也許你正在做Ad-Hoc /企業/個人構建,或者你足夠大膽地看到Apple對App Store的這個想法。 無論哪種方式,這是一個使用當前運行時和UIViewController 工作的實現 - 不需要設置屬性!

// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

@property (readonly, assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;

@end

// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@implementation UIViewController (isPresentedInPopover)

- (BOOL)isPresentedInPopover
{
    Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController");
    UIPopoverController *popover = object_getIvar(self, privatePopoverIvar);
    BOOL privateAPIValue = popover != nil;
    return privateAPIValue ?: [[self parentViewController] isPresentedInPopover];
}

@end

最有幫助的可能是使popover成為一個類變量,因此在將要呈現popover的類的.m文件中,執行以下操作:

    @interface ExampleViewController()
    @property (nonatomic, strong) UIPopoverController *popover
    @end

    @implementation
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([segue.identifier isEqualToString:@"some segue"])
        {
            //prevent stacking popovers
            if ([self.popover isPopoverVisible])
            {
                [self.popover dismissPopoverAnimated:YES];
                self.popover = nil;
            }
            [segue.destinationViewController setDelegate:self];
            self.popover = [(UIStoryboardPopoverSegue *)segue popoverController];
         }
     }
     @end

正如@joey在上面寫的那樣,Apple在iOS 8中消除了對虛擬控件的需求,其中為UIViewController定義的popoverPresentationController屬性為“視圖控制器層次結構中最近的祖先,它是一個彈出式表示控制器。(只讀)”。

以下是Swift中基於故事板定義的基於UIPopoverPresentationController的segue的示例。 在這種情況下,按鈕已經以編程方式添加,並且可以以這種方式定義為彈出窗口的錨點。 發件人也可以是選定的UITableViewCell或來自它的視圖。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "showCallout" {
        let button = sender as UIButton
        let calloutViewController = segue.destinationViewController as CalloutViewController
        if let popover = calloutViewController.popoverPresentationController {
            popover.sourceView = button
            popover.sourceRect = button.bounds
        }
    }
}

從ndoc的anwser起飛: 這個答案在iOS 6中顯示了一種更簡潔的方式,以防止彈出窗口顯示多個時間段。 鏈接中的方法對我來說非常有效,可以防止彈出堆疊。

如果你只想知道你的控制器是否出現在一個popover內(不想獲得對popover控制器的引用),你可以簡單地這樣做,不存儲變量也不會破解私有API。

-(BOOL)isPresentedInPopover
{
    for (UIView *superview = self.view.superview; superview != nil; superview = superview.superview)
    {
        if ([NSStringFromClass([superview class]) isEqualToString:@"_UIPopoverView"])
            return YES;
    }
    return NO;
}

暫無
暫無

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

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