UIViewControllerのインスタンスを使用して、それを提示するために使用されているUIPopoverControllerを見つける方法はありますか?また、最初にUIPopoverControllerを表示したUIViewControllerを見つけたいと思います。
私は通常、デリゲートまたは他の種類の通知を使用して、表示されたビューコントローラーから表示されているビューコントローラーに信号を送信しますが、この場合、ポップオーバーを閉じてから別のビューに移動する再利用可能なカスタムセグエを作成しようとしていますメインビューで。
これは簡単だと思うかもしれませんが(UIViewController
にはプライベートな_popoverController
プロパティさえあります!)、そうではありません。
一般的な答えは、UIPopoverController
が作成されたときに、それが提示しているUIViewController
にUIViewController
への参照を保存する必要があるということです。
プログラムでUIPopoverController
を作成している場合は、それがUIViewController
サブクラスに参照を格納するときです。
ストーリーボードとセグエを使用している場合は、UIPopoverController
メソッドでセグエからprepareForSegue
を取得できます。
UIPopoverController* popover = [(UIStoryboardPopoverSegue*)segue popoverController];
もちろん、あなたのセグエが本当にUIStoryboardPopoverSegueであることを確認してください!
独自のカスタムプロパティと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
@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
}
}
}
おそらく最も役立つのは、ポップオーバーをクラス変数にすることです。したがって、ポップオーバーを表示するクラスの.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
Ndocの回答からの脱却: この回答 は、ポップオーバーがセグエを介して複数回表示されるのを防ぐためのiOS6のより適切な方法を示しています。リンクのメソッドは、ポップオーバーのスタッキングを防ぐために私にとって素晴らしい働きをしたものでした。