20

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

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

4

6 に答える 6

25

これは簡単だと思うかもしれませんが ( にUIViewControllerは私有_popoverController財産があります!)、そうではありません。

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

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

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

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

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

于 2012-06-02T05:49:43.567 に答える
11

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

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

// 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それがポップオーバーにも含まれているかどうかを確認できます。このようにして、たとえば a にプロパティを設定するUINavigationControllerと、そのすべての子ビュー コントローラーが に正しく応答しisPresentedInPopoverます。サブクラスでこれを実現するには、すべての新しい子ビュー コントローラーでこれを設定しようとするか、ナビゲーション コントローラーをサブクラス化するか、その他の恐ろしいことを行う必要があります。

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

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

with scopeとUIViewController.h定義された ivar があることがわかります。幸いなことに、これはコンパイラによってのみ強制されます。ランタイムに関する限り、神聖なものは何もなく、どこからでもその ivar にアクセスするのは非常に簡単です。一貫性を保つために、プロパティへのアクセスごとにデバッグのみのランタイム チェックを追加します。UIPopoverController* _popoverController@package

// 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 が 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
于 2012-09-18T18:53:00.790 に答える
1

おそらく最も役立つのは、ポップオーバーをクラス変数にすることです。そのため、ポップオーバーを表示するクラスの .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
于 2012-09-06T02:38:09.613 に答える
0

ndoc の anwser からの脱却:この回答は、iOS 6 でセグエを介してポップオーバーが複数回表示されるのを防ぐためのより適切な方法を示しています。リンクの方法は、ポップオーバーのスタッキングを防ぐために私にとって非常にうまくいった方法でした。

于 2013-08-08T09:13:35.860 に答える
-1

コントローラーがポップオーバー内に表示されているかどうかだけを知りたい (ポップオーバー コントローラーへの参照を取得することに関心がない) 場合は、変数を保存したり、プライベート API をハッキングしたりすることなく、これを簡単に行うことができます。

-(BOOL)isPresentedInPopover
{
    for (UIView *superview = self.view.superview; superview != nil; superview = superview.superview)
    {
        if ([NSStringFromClass([superview class]) isEqualToString:@"_UIPopoverView"])
            return YES;
    }
    return NO;
}
于 2013-08-14T21:36:52.870 に答える