web-dev-qa-db-ja.com

ポップオーバーに表示されているUIViewControllerからUIPopoverControllerを見つけるにはどうすればよいですか?

UIViewControllerのインスタンスを使用して、それを提示するために使用されているUIPopoverControllerを見つける方法はありますか?また、最初にUIPopoverControllerを表示したUIViewControllerを見つけたいと思います。

私は通常、デリゲートまたは他の種類の通知を使用して、表示されたビューコントローラーから表示されているビューコントローラーに信号を送信しますが、この場合、ポップオーバーを閉じてから別のビューに移動する再利用可能なカスタムセグエを作成しようとしていますメインビューで。

19
SoftMemes

これは簡単だと思うかもしれませんが(UIViewControllerにはプライベートな_popoverControllerプロパティさえあります!)、そうではありません。

一般的な答えは、UIPopoverControllerが作成されたときに、それが提示しているUIViewControllerUIViewControllerへの参照を保存する必要があるということです。

  1. プログラムでUIPopoverControllerを作成している場合は、それがUIViewControllerサブクラスに参照を格納するときです。

  2. ストーリーボードとセグエを使用している場合は、UIPopoverControllerメソッドでセグエからprepareForSegueを取得できます。

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

もちろん、あなたのセグエが本当にUIStoryboardPopoverSegueであることを確認してください!

25
Christophe

独自のカスタムプロパティとUIKitのプライベートAPIの組み合わせを活用することをお勧めします。アプリストアの拒否を回避するには、プライベートAPIをリリースビルド用にコンパイルし、実装を確認するためにのみ使用する必要があります。

まず、カスタムプロパティをUIViewControllerのカテゴリに組み込みましょう。これにより、実装にいくつかの特典が与えられ、戻ってすべてのクラスをカスタムViewControllerサブクラスから派生させる必要はありません。

// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

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

@end

次に、実装について説明します。ObjectiveCランタイムに関連付けられたオブジェクト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に正しく応答します。サブクラスでこれを実現するには、すべての新しい子ビューコントローラーでこれを設定するか、ナビゲーションコントローラーをサブクラス化するか、その他の恐ろしいことを試みます。

その他のランタイムマジック

この特定の問題に対してObjectiveCランタイムが提供しなければならないことはまだまだあり、それらを使用してAppleのプライベート実装の詳細にジャンプし、独自のアプリをチェックすることができます。リリースビルドの場合、この余分なコードはコンパイルされるので、のすべてを見通す目を心配する必要はありません サウロン Appleストアに送信するとき。

UIViewController.hから、UIPopoverController* _popoverControllerスコープで@packageとして定義されたivarがあることがわかります。幸いなことに、これはコンパイラによってのみ強制されます。 Nothingランタイムに関する限り、神聖であり、どこからでもその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に設定してコンパイルすると、以前とまったく同じコードにコンパイルされます。

無料と愚か者のために

アドホック/エンタープライズ/パーソナルビルドを行っているか、AppleがAppStoreでこれについてどう考えているかを確認するのに十分な大胆さを持っているかもしれません。いずれにせよ、これが実装です。 正常に機能します現在のランタイムと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
11
Sterling Archer

@joeyが上で書いたように、Appleは、iOS8でダミーコントロールの必要性を排除しました。popoverPresentationControllerプロパティはUIViewControllerに対して「最も近い祖先」として定義されています。ポップオーバープレゼンテーションコントローラであるビューコントローラ階層。(読み取り専用)」。

これは、ストーリーボードで定義されたUIPopoverPresentationControllerベースのセグエのSwiftの例です。この場合、ボタンはプログラムで追加されており、この方法で次のように定義できます。ポップオーバーのアンカー。送信者は、選択された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
        }
    }
}
1
Jeff Collier

おそらく最も役立つのは、ポップオーバーをクラス変数にすることです。したがって、ポップオーバーを表示するクラスの.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
1
ndoc

Ndocの回答からの脱却: この回答 は、ポップオーバーがセグエを介して複数回表示されるのを防ぐためのiOS6のより適切な方法を示しています。リンクのメソッドは、ポップオーバーのスタッキングを防ぐために私にとって素晴らしい働きをしたものでした。

0