web-dev-qa-db-fra.com

Comment rejeter un Popover Storyboard

J'ai créé un popover à partir d'un UIBarButtonItem en utilisant des Storyboards Xcode (donc il n'y a pas de code) comme ceci:

Xcode 5.0 Connections Inspector with Popover

La présentation du popover fonctionne très bien. Cependant, je ne peux pas faire disparaître le popover lorsque j'appuie sur le UIBarButtonItem qui l'a fait apparaître.

Lorsque le bouton est enfoncé (première fois), le popover apparaît. Lorsque le bouton est pressé à nouveau (deuxième fois), le même popover apparaît dessus, donc maintenant j'ai deux popovers (ou plus si je continue à appuyer sur le bouton). Selon les iOS Human Interface Guidelines Je dois faire apparaître le popover au premier tap et disparaître au second:

Assurez-vous qu'un seul popover est visible à l'écran à la fois. Vous ne devez pas afficher plus d'un popover (ou vue personnalisée conçue pour ressembler et se comporter comme un popover) en même temps. En particulier, vous devez éviter d'afficher simultanément une cascade ou une hiérarchie de popovers, dans laquelle un popover émerge d'un autre.

Comment puis-je fermer la fenêtre contextuelle lorsque l'utilisateur appuie sur UIBarButtonItem pour la deuxième fois?

73
Samuel Spencer

EDIT: Ces problèmes semblent être résolus à partir d'iOS 7.1/Xcode 5.1.1. (Peut-être plus tôt, car je n'ai pas pu tester toutes les versions. Certainement après iOS 7.0, car j'ai testé celle-ci.) Lorsque vous créez un enchaînement popover à partir d'un UIBarButtonItem, le enchaînement s'assure que le fait d'appuyer sur le popover masque à nouveau le popover plutôt que d'afficher un doublon. Cela fonctionne bien pour les nouvelles séquences de popover basées sur UIPresentationController que Xcode 6 crée également pour iOS 8.

Étant donné que ma solution peut présenter un intérêt historique pour ceux qui prennent toujours en charge les versions iOS antérieures, je l'ai laissée ci-dessous.


Si vous stockez une référence au contrôleur popover de la séquence, supprimez-la avant de la définir sur une nouvelle valeur lors des appels répétés de prepareForSegue:sender:, tout ce que vous évitez est le problème d'obtenir plusieurs popovers d'empilement lors d'appuis répétés sur le bouton - vous ne pouvez toujours pas utiliser le bouton pour fermer le popover comme le recommande le HIG (et comme on le voit dans les applications d'Apple, etc.)

Vous pouvez tirer parti de la réduction à zéro des références faibles ARC pour une solution simple, cependant:

1: Segue à partir du bouton

À partir d'iOS 5, vous ne pouviez pas faire fonctionner cela avec un enchaînement à partir d'un UIBarButtonItem, mais vous pouvez le faire sur iOS 6 et versions ultérieures. (Sur iOS 5, vous devez vous séparer du contrôleur de vue lui-même, puis appeler l'action du bouton performSegueWithIdentifier: après avoir vérifié le popover.)

2: utilisez une référence au popover dans -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: Il n'y a pas de troisième étape!

La bonne chose à propos de l'utilisation d'une référence faible de mise à zéro ici est qu'une fois le contrôleur popover fermé - que ce soit par programme dans shouldPerformSegueWithIdentifier:, ou automatiquement par l'utilisateur tapant ailleurs en dehors du popover - l'ivar retourne à nil, donc nous revenons à notre état initial.

Sans mettre à zéro les références faibles, il faudrait aussi:

  • ensemble myPopover = nil lors de son rejet dans shouldPerformSegueWithIdentifier:, et
  • se définir comme délégué du contrôleur popover afin d'attraper popoverControllerDidDismissPopover: et définissez également myPopover = nil là (donc nous interceptons lorsque le popover est automatiquement fermé).
114
rickster

J'ai trouvé la solution ici https://stackoverflow.com/a/7938513/665396 Dans first prepareForSegue: sender: stocke dans un ivar/property le pointeur vers l'UIPopoverController et l'utilisateur qui pointera pour fermer le popover dans les invocations suivantes.

...
@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

J'ai utilisé une séquence personnalisée pour cela.

1

créer une séquence personnalisée à utiliser dans 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

dans le contrôleur de vue qui est la source/entrée de la séquence, par ex. commencer enchaînement avec action:

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

3

les références sont attribuées par segue, ce qui crée UIPopoverController - lors de la fermeture de popover

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

cordialement, Peter

2
Peter Blazejewicz

J'ai résolu ce problème sans avoir besoin de conserver une copie d'un UIPopoverController. Manipulez simplement tout dans le storyboard (barre d'outils, boutons de barre, etc.), et

  • gérer la visibilité du popover par un booléen,
  • assurez-vous qu'il y a un délégué, et il est réglé sur self

Voici tout le code:

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

Je l'ai résolu en créant un ixPopoverBarButtonItem personnalisé qui déclenche la séquence ou rejette le popover affiché.

Ce que je fais: je bascule l'action et la cible du bouton, de sorte qu'il déclenche la séquence, ou supprime le popover actuellement affiché.

Il m'a fallu beaucoup de recherches pour cette solution, je ne veux pas prendre le crédit pour l'idée de basculer l'action. Mettre le code dans un bouton personnalisé était mon approche pour garder le code passe-partout à mon avis au minimum.

Dans le storyboard, je définis la classe du BarButtonItem à ma classe personnalisée:

custom bar button

Ensuite, je passe le popover créé par la séquence à mon implémentation de bouton personnalisé dans le prepareForSegue:sender: méthode:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender  
{
    if ([segue.identifier isEqualToString:@"myPopoverSegue"]) {
        UIStoryboardPopoverSegue* popSegue = (UIStoryboardPopoverSegue*)segue;
        [(ixPopoverBarButtonItem *)sender showingPopover:popSegue.popoverController];
    }
}

Btw ... puisque j'ai plus d'un bouton déclenchant des popovers, je dois toujours garder une référence du popover actuellement affiché et le supprimer lorsque je rend le nouveau visible, mais ce n'était pas votre question ...

Voici comment j'ai implémenté mon UIBarButtonItem personnalisé:

...interface:

@interface ixPopoverBarButtonItem : UIBarButtonItem

- (void) showingPopover:  (UIPopoverController *)popoverController;

@end

... et 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: je suis nouveau sur ARC, donc je ne suis pas tout à fait sûr si je fuit ici. Veuillez me dire si je suis ...

2

J'ai pris la réponse de rickster et l'ai empaquetée dans une classe dérivée d'UIViewController. Cette solution nécessite les éléments suivants:

  • iOS 6 (ou version ultérieure) avec ARC
  • Dérivez votre contrôleur de vue de cette classe
  • assurez-vous d'appeler les "super" versions de prepareForSegue: sender et shouldPerformSegueWithIdentifier: sender si vous remplacez ces méthodes
  • Utiliser une séquence popover nommée

La bonne chose à ce sujet est que vous n'avez pas à faire de codage "spécial" pour prendre en charge la bonne gestion des Popovers.

Interface :

@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

Implémentation :

@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

Source disponible sur GitHub

1
Tod Cunningham