web-dev-qa-db-ja.com

ストーリーボードポップオーバーを閉じる方法

Xcode Storyboardsを使用してUIBarButtonItemからポップオーバーを作成しました(したがって、コードはありません)。

Xcode 5.0 Connections Inspector with Popover

ポップオーバーの提示は問題なく機能します。しかし、表示されたUIBarButtonItemをタップすると、disappearにポップオーバーを取得できません。

ボタンを押すと(初めて)、ポップオーバーが表示されます。ボタンをもう一度押すと(2回目)同じポップオーバーがその上に表示されるので、2つのポップオーバーがあります(ボタンを押し続けるとさらに多くなります)。 iOS Human Interface Guidelines によると、ポップオーバーを最初のタップで表示し、2回目のタップで非表示にする必要があります:

一度に1つのポップオーバーのみが画面に表示されるようにします。同時に複数のポップオーバー(または、ポップオーバーのように見えて動作するように設計されたカスタムビュー)を表示しないでください。特に、ポップオーバーのカスケードまたは階層を同時に表示することは避けてください。1つのポップオーバーが別のポップオーバーから出現します。

ユーザーがUIBarButtonItemをもう一度タップしたときにポップオーバーを閉じるにはどうすればよいですか?

73
Samuel Spencer

編集:これらの問題は、iOS 7.1/Xcode 5.1.1で修正されたようです。 (おそらく、以前のバージョンでは、すべてのバージョンをテストできなかったためです。iOS7.0の後、間違いなくテストしました。)UIBarButtonItemからポップオーバーセグエを作成すると、セグエはポップオーバーをもう一度タップするとポップオーバーが非表示になることを確認します重複を示すのではなく。 Xcode 6がiOS 8用に作成する新しいUIPresentationControllerベースのポップオーバーセグエに対しても正しく機能します。

私のソリューションは、以前のiOSバージョンをまだサポートしている人々にとって歴史的な興味があるかもしれないので、以下に残しました。


セグエのポップオーバーコントローラーへの参照を保存し、prepareForSegue:sender:の繰り返し呼び出しで新しい値に設定する前にそれを破棄すると、ボタンを繰り返し押すと複数のスタッキングポップオーバーが発生する問題が回避されます。 HIGが推奨するように(およびAppleのアプリなどで見られるように)ボタンを使用してポップオーバーを閉じることはできません

ただし、ARCゼロ化の弱参照を利用して、簡単なソリューションを実現できます。

1:ボタンからのセグエ

IOS 5の時点では、UIBarButtonItemのセグエではこの機能を使用できませんでしたが、iOS 6以降では使用できます。 (iOS 5では、View Controller自体から分離し、ポップオーバーを確認した後にボタンのアクションをperformSegueWithIdentifier:に呼び出す必要があります。)

2:-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

3:ステップ3はありません!

ここでゼロ化ウィークリファレンスを使用することの良い点は、ポップオーバーコントローラーがいったん終了すると(プログラムでshouldPerformSegueWithIdentifier:で、またはユーザーがポップオーバーの外部のどこかを自動的にタップして)、ivarが再びnilに移動することです。最初の状態に戻りました。

弱参照をゼロ化せずに、次のことも行う必要があります。

  • myPopover = nilで閉じるときにshouldPerformSegueWithIdentifier:を設定し、
  • popoverControllerDidDismissPopover:をキャッチするためにポップオーバーコントローラーのデリゲートとして自分自身を設定し、そこにmyPopover = nilを設定します(したがって、ポップオーバーが自動的に終了するときにキャッチします)。
114
rickster

ここで解決策を見つけました 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;
...
}
13
jorgecarreira

これにはカスタムセグエを使用しました。

1

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

2

セグエのソース/入力であるView Controllerでアクションでセグエを開始します。

-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
    if(nil == self.recentSearchesPopoverController)
    {
        NSString *identifier = NSStringFromClass([CustomPopoverSegue class]);
        [self performSegueWithIdentifier:identifier sender:self];
    } 
}

3

uIPopoverControllerを作成するセグエによって参照が割り当てられます-ポップオーバーを閉じるとき

-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
    if(self.recentSearchesPopoverController)
    {
        [self.recentSearchesPopoverController dismissPopoverAnimated:YES];
        self.recentSearchesPopoverController = nil;
    }    
}

よろしく、ピーター

2

UIPopoverControllerのコピーを保持する必要なく、この問題を解決しました。ストーリーボードのすべて(ツールバー、BarButtonsなど)を処理するだけで、

  • ブールオーバーによるポップオーバーの可視性の処理、
  • デリゲートが存在し、selfに設定されていることを確認してください

すべてのコードは次のとおりです。

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
2
Q8i

セグエをトリガーするか、表示されているポップオーバーを閉じるカスタムixPopoverBarButtonItemを作成して解決しました。

対処方法:ボタンのアクションとターゲットを切り替えて、セグエをトリガーするか、現在表示されているポップオーバーを破棄します。

この解決策には多くのグーグルが必要でしたが、アクションを切り替えるというアイデアの功績は認めたくありません。コードをカスタムボタンに配置することは、ボイラープレートコードを最小限に抑えるための私のアプローチでした。

ストーリーボードで、BarButtonItemのクラスをカスタムクラスに定義します。

custom bar button

次に、セグエによって作成されたポップオーバーを、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に慣れていないので、ここでリークしているかどうかは完全にはわかりません。もし私が...

2

Ricksterの答えを取り、UIViewControllerから派生したクラスにパッケージ化しました。このソリューションには以下が必要です。

  • aRCを搭載したiOS 6(以降)
  • このクラスからView Controllerを導出します
  • これらのメソッドをオーバーライドする場合は、prepareForSegue:senderおよびshouldPerformSegueWithIdentifier:senderの「スーパー」バージョンを必ず呼び出してください。
  • 名前付きポップオーバーセグエを使用する

これの良いところは、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

GitHubで入手可能なソース

1
Tod Cunningham