J'ai créé un popover à partir d'un UIBarButtonItem
en utilisant des Storyboards Xcode (donc il n'y a pas de code) comme ceci:
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?
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:
À 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.)
-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
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:
myPopover = nil
lors de son rejet dans shouldPerformSegueWithIdentifier:
, etpopoverControllerDidDismissPopover:
et définissez également myPopover = nil
là (donc nous interceptons lorsque le popover est automatiquement fermé).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;
...
}
J'ai utilisé une séquence personnalisée pour cela.
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
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];
}
}
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
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
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
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:
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 ...
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:
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