Xcode Storyboardsを使用してUIBarButtonItem
からポップオーバーを作成しました(したがって、コードはありません)。
ポップオーバーの提示は問題なく機能します。しかし、表示されたUIBarButtonItem
をタップすると、disappearにポップオーバーを取得できません。
ボタンを押すと(初めて)、ポップオーバーが表示されます。ボタンをもう一度押すと(2回目)同じポップオーバーがその上に表示されるので、2つのポップオーバーがあります(ボタンを押し続けるとさらに多くなります)。 iOS Human Interface Guidelines によると、ポップオーバーを最初のタップで表示し、2回目のタップで非表示にする必要があります:
一度に1つのポップオーバーのみが画面に表示されるようにします。同時に複数のポップオーバー(または、ポップオーバーのように見えて動作するように設計されたカスタムビュー)を表示しないでください。特に、ポップオーバーのカスケードまたは階層を同時に表示することは避けてください。1つのポップオーバーが別のポップオーバーから出現します。
ユーザーがUIBarButtonItem
をもう一度タップしたときにポップオーバーを閉じるにはどうすればよいですか?
編集:これらの問題は、iOS 7.1/Xcode 5.1.1で修正されたようです。 (おそらく、以前のバージョンでは、すべてのバージョンをテストできなかったためです。iOS7.0の後、間違いなくテストしました。)UIBarButtonItem
からポップオーバーセグエを作成すると、セグエはポップオーバーをもう一度タップするとポップオーバーが非表示になることを確認します重複を示すのではなく。 Xcode 6がiOS 8用に作成する新しいUIPresentationController
ベースのポップオーバーセグエに対しても正しく機能します。
私のソリューションは、以前のiOSバージョンをまだサポートしている人々にとって歴史的な興味があるかもしれないので、以下に残しました。
セグエのポップオーバーコントローラーへの参照を保存し、prepareForSegue:sender:
の繰り返し呼び出しで新しい値に設定する前にそれを破棄すると、ボタンを繰り返し押すと複数のスタッキングポップオーバーが発生する問題が回避されます。 HIGが推奨するように(およびAppleのアプリなどで見られるように)ボタンを使用してポップオーバーを閉じることはできません
ただし、ARCゼロ化の弱参照を利用して、簡単なソリューションを実現できます。
IOS 5の時点では、UIBarButtonItem
のセグエではこの機能を使用できませんでしたが、iOS 6以降では使用できます。 (iOS 5では、View Controller自体から分離し、ポップオーバーを確認した後にボタンのアクションをperformSegueWithIdentifier:
に呼び出す必要があります。)
-shouldPerformSegue...
のポップオーバーへの参照を使用します@interface ViewController
@property (weak) UIPopoverController *myPopover;
@end
@implementation ViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// if you have multiple segues, check segue.identifier
self.myPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if (self.myPopover) {
[self.myPopover dismissPopoverAnimated:YES];
return NO;
} else {
return YES;
}
}
@end
ここでゼロ化ウィークリファレンスを使用することの良い点は、ポップオーバーコントローラーがいったん終了すると(プログラムでshouldPerformSegueWithIdentifier:
で、またはユーザーがポップオーバーの外部のどこかを自動的にタップして)、ivarが再びnil
に移動することです。最初の状態に戻りました。
弱参照をゼロ化せずに、次のことも行う必要があります。
myPopover = nil
で閉じるときにshouldPerformSegueWithIdentifier:
を設定し、popoverControllerDidDismissPopover:
をキャッチするためにポップオーバーコントローラーのデリゲートとして自分自身を設定し、そこにmyPopover = nil
を設定します(したがって、ポップオーバーが自動的に終了するときにキャッチします)。ここで解決策を見つけました https://stackoverflow.com/a/7938513/665396 最初のprepareForSegue:sender:UIPopoverControllerへのポインターとポップオーバーを閉じるユーザー後続の呼び出しで。
...
@property (nonatomic, weak) UIPopoverController* storePopover;
...
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender {
if ([segue.identifier isEqualToString:@"My segue"]) {
// setup segue here
[self.storePopover dismissPopoverAnimated:YES];
self.storePopover = ((UIStoryboardPopoverSegue*)segue).popoverController;
...
}
これにはカスタムセグエを使用しました。
storyboardで使用するカスタムセグエを作成します。
@implementation CustomPopoverSegue
-(void)perform
{
// "onwer" of popover - it needs to use "strong" reference to retain UIPopoverReference
ToolbarSearchViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
// create UIPopoverController
UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:destination];
// source is delegate and owner of popover
popoverController.delegate = source;
popoverController.passthroughViews = [NSArray arrayWithObject:source.searchBar];
source.recentSearchesPopoverController = popoverController;
// present popover
[popoverController presentPopoverFromRect:source.searchBar.bounds
inView:source.searchBar
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
@end
セグエのソース/入力であるView Controllerでアクションでセグエを開始します。
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
if(nil == self.recentSearchesPopoverController)
{
NSString *identifier = NSStringFromClass([CustomPopoverSegue class]);
[self performSegueWithIdentifier:identifier sender:self];
}
}
uIPopoverControllerを作成するセグエによって参照が割り当てられます-ポップオーバーを閉じるとき
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
if(self.recentSearchesPopoverController)
{
[self.recentSearchesPopoverController dismissPopoverAnimated:YES];
self.recentSearchesPopoverController = nil;
}
}
よろしく、ピーター
UIPopoverController
のコピーを保持する必要なく、この問題を解決しました。ストーリーボードのすべて(ツールバー、BarButtonsなど)を処理するだけで、
すべてのコードは次のとおりです。
ViewController.h
@interface ViewController : UIViewController <UIPopoverControllerDelegate>
@end
ViewController.m
@interface ViewController ()
@property BOOL isPopoverVisible;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.isPopoverVisible = NO;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// add validations here...
self.isPopoverVisible = YES;
[[(UIStoryboardPopoverSegue*)segue popoverController] setDelegate:self];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
return !self.isPopoverVisible;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
self.isPopoverVisible = NO;
}
@end
セグエをトリガーするか、表示されているポップオーバーを閉じるカスタムixPopoverBarButtonItem
を作成して解決しました。
対処方法:ボタンのアクションとターゲットを切り替えて、セグエをトリガーするか、現在表示されているポップオーバーを破棄します。
この解決策には多くのグーグルが必要でしたが、アクションを切り替えるというアイデアの功績は認めたくありません。コードをカスタムボタンに配置することは、ボイラープレートコードを最小限に抑えるための私のアプローチでした。
ストーリーボードで、BarButtonItemのクラスをカスタムクラスに定義します。
次に、セグエによって作成されたポップオーバーを、prepareForSegue:sender:
方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"myPopoverSegue"]) {
UIStoryboardPopoverSegue* popSegue = (UIStoryboardPopoverSegue*)segue;
[(ixPopoverBarButtonItem *)sender showingPopover:popSegue.popoverController];
}
}
ところで...ポップオーバーをトリガーするボタンが複数あるので、現在表示されているポップオーバーの参照を保持し、新しいものを表示するときにそれを閉じる必要がありますが、これはあなたの質問ではありません...
カスタムUIBarButtonItemの実装方法は次のとおりです。
...インタフェース:
@interface ixPopoverBarButtonItem : UIBarButtonItem
- (void) showingPopover: (UIPopoverController *)popoverController;
@end
...およびimpl:
#import "ixPopoverBarButtonItem.h"
@interface ixPopoverBarButtonItem ()
@property (strong, nonatomic) UIPopoverController *popoverController;
@property (nonatomic) SEL tempAction;
@property (nonatomic,assign) id tempTarget;
- (void) dismissPopover;
@end
@implementation ixPopoverBarButtonItem
@synthesize popoverController = _popoverController;
@synthesize tempAction = _tempAction;
@synthesize tempTarget = _tempTarget;
-(void)showingPopover:(UIPopoverController *)popoverController {
self.popoverController = popoverController;
self.tempAction = self.action;
self.tempTarget = self.target;
self.action = @selector(dismissPopover);
self.target = self;
}
-(void)dismissPopover {
[self.popoverController dismissPopoverAnimated:YES];
self.action = self.tempAction;
self.target = self.tempTarget;
self.popoverController = nil;
self.tempAction = nil;
self.tempTarget = nil;
}
@end
ps:私はARCに慣れていないので、ここでリークしているかどうかは完全にはわかりません。もし私が...
Ricksterの答えを取り、UIViewControllerから派生したクラスにパッケージ化しました。このソリューションには以下が必要です。
これの良いところは、Popoverの適切な処理をサポートするために「特別な」コーディングを行う必要がないことです。
インターフェース:
@interface FLStoryboardViewController : UIViewController
{
__strong NSString *m_segueIdentifier;
__weak UIPopoverController *m_popoverController;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender;
@end
実装:
@implementation FLStoryboardViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if( [segue isKindOfClass:[UIStoryboardPopoverSegue class]] )
{
UIStoryboardPopoverSegue *popoverSegue = (id)segue;
if( m_popoverController == nil )
{
assert( popoverSegue.identifier.length > 0 ); // The Popover segue should be named for this to work fully
m_segueIdentifier = popoverSegue.identifier;
m_popoverController = popoverSegue.popoverController;
}
else
{
[m_popoverController dismissPopoverAnimated:YES];
m_segueIdentifier = nil;
m_popoverController = nil;
}
}
else
{
[super prepareForSegue:segue sender:sender];
}
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
// If this is an unnamed segue go ahead and allow it
if( identifier.length != 0 )
{
if( [identifier compare:m_segueIdentifier] == NSOrderedSame )
{
if( m_popoverController == NULL )
{
m_segueIdentifier = nil;
return YES;
}
else
{
[m_popoverController dismissPopoverAnimated:YES];
m_segueIdentifier = nil;
m_popoverController = nil;
return NO;
}
}
}
return [super shouldPerformSegueWithIdentifier:identifier sender:sender];
}
@end