ios objective-c uipopovercontroller uibarbuttonitem uistoryboard

ios - Cómo descartar un cuento Popover



objective-c uipopovercontroller (6)

Encontré la solución aquí https://stackoverflow.com/a/7938513/665396 En el primer prepareForSegue: remitente: almacene en un ivar / property el puntero al UIPopoverController y use ese puntero para descartar el popover en las invocaciones posteriores.

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

UIBarButtonItem un popover desde UIBarButtonItem usando Xcode Storyboards (así que no hay código) como este:

Presentar el popover funciona bien. Sin embargo, no puedo hacer desaparecer el popover cuando UIBarButtonItem el UIBarButtonItem que lo hizo aparecer.

Cuando se presiona el botón (primera vez) aparece el popover. Cuando se presiona el botón otra vez (por segunda vez) aparece el mismo popover en la parte superior, por lo que ahora tengo dos popovers (o más si continúo presionando el botón). De acuerdo con las Directrices de interfaz humana de iOS , necesito hacer que el popover aparezca en el primer toque y desaparezca en el segundo:

Asegúrese de que solo haya un popover visible en pantalla a la vez. No debe mostrar más de un popover (o vista personalizada diseñada para verse y comportarse como un popover) al mismo tiempo. En particular, debe evitar mostrar una cascada o jerarquía de popovers simultáneamente, en la que un popover emerge de otro.

¿Cómo puedo descartar el popover cuando el usuario toca el UIBarButtonItem por segunda vez?


He resuelto este problema sin necesidad de guardar una copia de UIPopoverController . Simplemente maneje todo en el guión gráfico (Toolbar, BarButtons, etc.), y

  • manejar la visibilidad del popover por un booleano,
  • asegúrese de que haya un delegado, y está configurado a uno mismo

Aquí está todo el código:

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


He usado segue personalizado para esto.

1

crear segue personalizado para usar en 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

Controlador de vista que es fuente / entrada de segue, por ejemplo, iniciar segue con acción:

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

3

las referencias se asignan por segue, lo que crea UIPopoverController - al descartar popover

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

saludos, Peter


Lo resolví creando un ixPopoverBarButtonItem personalizado que activa el segue o descarta el popover que se muestra.

Lo que hago: alternar la acción y el objetivo del botón, por lo que desencadena el segue o elimina el popover que se muestra actualmente.

Me tomó mucho buscar en Google para esta solución, no quiero tomar los créditos por la idea de alternar la acción. Poner el código en un botón personalizado fue mi enfoque para mantener el código repetitivo en mi opinión al mínimo.

En el guión gráfico, defino la clase del BarButtonItem en mi clase personalizada:

Luego paso el popover creado por el segue a la implementación de mi botón personalizado en el prepareForSegue:sender: :

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

Por cierto ... ya que tengo más de un botón disparando popovers, todavía tengo que mantener una referencia del popover que se muestra actualmente y descartarlo cuando hago el nuevo visible, pero esta no era tu pregunta ...

Así es como implementé mi UIBarButtonItem personalizado:

...interfaz:

@interface ixPopoverBarButtonItem : UIBarButtonItem - (void) showingPopover: (UIPopoverController *)popoverController; @end

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

PD: Soy nuevo en ARC, así que no estoy del todo seguro de si estoy filtrando aquí. Por favor dime si soy ...


Tomé la respuesta de Rickster y la empaqué en una clase derivada de UIViewController. Esta solución requiere lo siguiente:

  • iOS 6 (o posterior) con ARC
  • Derive su controlador de vista de esta clase
  • asegúrese de llamar a las versiones "super" de prepareForSegue: sender y shouldPerformSegueWithIdentifier: sender si está anulando esos métodos
  • Use un popover con nombre segue

Lo bueno de esto es que no tienes que hacer ninguna codificación "especial" para apoyar el manejo correcto de Popovers.

Interfaz :

@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

Implementación :

@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

Fuente disponible en GitHub


EDITAR: Estos problemas parecen estar corregidos desde iOS 7.1 / Xcode 5.1.1. (Posiblemente antes, ya que no he podido probar todas las versiones. Definitivamente después de iOS 7.0, ya que probé esa). Cuando creas un popover segue desde un UIBarButtonItem , el segue asegura que al tocar el popover nuevamente se oculta el popover en lugar de mostrar un duplicado Funciona correctamente para los nuevos UIPresentationController de popover basados ​​en UIPresentationController que Xcode 6 crea para iOS 8 también.

Dado que mi solución puede ser de interés histórico para aquellos que todavía soportan versiones anteriores de iOS, la he dejado a continuación.

Si almacena una referencia al controlador de popover de segue, desechándolo antes de establecerlo en un nuevo valor al repetir invocaciones de prepareForSegue:sender: todo lo que evita es el problema de obtener múltiples popovers de apilamiento al presionar repetidamente el botón - usted todavía no puede usar el botón para descartar el popover como lo recomienda el HIG (y como se ve en las aplicaciones de Apple, etc.)

Sin embargo, puede aprovechar las referencias débiles de puesta a cero ARC para una solución simple:

1: Segue desde el botón

A partir de iOS 5, no podría hacer que esto funcione con una segue de UIBarButtonItem , pero puede UIBarButtonItem en iOS 6 y versiones posteriores. (En iOS 5, debe realizar la transición desde el controlador de vista en sí, luego hacer que la llamada de acción del botón performSegueWithIdentifier: después de verificar el popover).

2: Use una referencia al popover en -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: ¡No hay paso tres!

Lo bueno de utilizar una referencia débil de puesta a cero aquí es que una vez que el controlador popover se descarta, ya sea programáticamente en shouldPerformSegueWithIdentifier: o automáticamente por el usuario tocando en otro lugar fuera del popover, el ivar vuelve a nil , así que estamos de regreso a nuestro estado inicial.

Sin poner a cero las referencias débiles, también tendríamos que:

  • establezca myPopover = nil al descartarlo en shouldPerformSegueWithIdentifier: y
  • establecerse como el delegado del controlador de popover para atrapar popoverControllerDidDismissPopover: y también establecer myPopover = nil allí (para que capturemos cuando el popover se descarta automáticamente).