objective-c ios uipopovercontroller autorotate uipopover

objective c - ¿Cómo hacer que UIPopoverController mantenga la misma posición después de girar?



objective-c ios (12)

A partir de iOS 8.0.2 willRotateToInterfaceOrientation no tendrá ningún efecto . Como se mencionó mhrrt, debe usar el método de delegado:

- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view

Por ejemplo, si desea que su ventana emergente aparezca directamente debajo de un botón que se presionó, usaría el siguiente código:

- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view { CGRect rectInView = [self.theButton convertRect:self.theButton.frame toView:self.view]; *rect = CGRectMake(CGRectGetMidX(rectInView), CGRectGetMaxY(rectInView), 1, 1); *view = self.view; }

No puedo mantener la ventana emergente en la misma posición en la pantalla después de la rotación. ¿Hay alguna buena manera de hacerlo, porque simplemente configurar un cuadro para desplegar funciona de manera terrible después de rotarlo? popover.frame = CGRectMake(someFrame); Después de la rotación, la ventana emergente se ve bien solo si está en el centro de la pantalla.


Apple tiene una sesión de preguntas y respuestas sobre exactamente este problema. Tu puedes encontrar los detalles aqui:

Preguntas y respuestas técnicas QA1694 Manejo de controladores de popover durante cambios de orientación

Básicamente, la técnica explica que en el método didRotateFromInterfaceOrientation su controlador de vista, didRotateFromInterfaceOrientation la didRotateFromInterfaceOrientation emergente de nuevo de la siguiente manera:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { [aPopover presentPopoverFromRect:targetRect.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; }

Para obtener más información, lea el artículo anterior y también la referencia de clase UIPopoverController :

Si el usuario gira el dispositivo mientras está visible una ventana emergente, el controlador de la ventana emergente oculta la ventana emergente y luego la muestra nuevamente al final de la rotación. El controlador de ventanas emergentes intenta colocar la ventana emergente de manera adecuada para usted, pero es posible que tenga que presentarla nuevamente u ocultarla por completo en algunos casos. Por ejemplo, cuando se muestra desde un elemento del botón de barra, el controlador de ventana emergente ajusta automáticamente la posición (y potencialmente el tamaño) de la ventana emergente para tener en cuenta los cambios en la posición del elemento del botón de barra. Sin embargo, si elimina el elemento del botón de la barra durante la rotación, o si presenta la ventana emergente desde un rectángulo de destino en una vista, el controlador de la ventana emergente no intenta reposicionar la ventana emergente. En esos casos, debe ocultar manualmente la ventana emergente o presentarla nuevamente desde una nueva posición apropiada. Puede hacer esto en el método didRotateFromInterfaceOrientation: del controlador de vista que utilizó para presentar la ventana emergente.


En iOS 7 puede usar - (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view para reposicionar la vista de UIPopoverController en la interfaz.

Consulte la documentation UIPopoverControllerDelegate .


He intentado simplemente establecer nuevo rect (rect.initialize (...)) y funciona.

func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer<CGRect>, inView view: AutoreleasingUnsafeMutablePointer<UIView?>) { if popoverPresentationController.presentedViewController.view.tag == Globals.PopoverTempTag { rect.initialize(getForPopupSourceRect()) } }


Para Swift:

func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>) { rect.pointee = CGRect(x: self.view.frame.size.width, y: 0, width: 1, height: 1) // Set new rect here }


Para iOS> 8, la respuesta de John Strickers ayudó pero no hizo lo que quería que hiciera.

Aquí está la solución que funcionó para mí. (Si desea descargar un proyecto de muestra completo, haga clic aquí: https://github.com/appteur/uipopoverExample )

Creé una propiedad para mantener cualquier elemento emergente que quería presentar y también agregué una propiedad para rastrear sourceRect y otra para la vista del botón al que quería que apuntara la flecha emergente.

@property (nonatomic, weak) UIView *activePopoverBtn; @property (nonatomic, strong) PopoverViewController *popoverVC; @property (nonatomic, assign) CGRect sourceRect;

El botón que activó mi ventana emergente está en una barra de herramientas. Cuando se toca, ejecuta el siguiente método que crea e inicia la ventana emergente.

-(void) buttonAction:(id)sender event:(UIEvent*)event { NSLog(@"ButtonAction"); // when the button is tapped we want to display a popover, so setup all the variables needed and present it here // get a reference to which button''s view was tapped (this is to get // the frame to update the arrow to later on rotation) // since UIBarButtonItems don''t have a ''frame'' property I found this way is easy UIView *buttonView = [[event.allTouches anyObject] view]; // set our tracker properties for when the orientation changes (handled in the viewWillTransitionToSize method above) self.activePopoverBtn = buttonView; self.sourceRect = buttonView.frame; // get our size, make it adapt based on our view bounds CGSize viewSize = self.view.bounds.size; CGSize contentSize = CGSizeMake(viewSize.width, viewSize.height - 100.0); // set our popover view controller property self.popoverVC = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"PopoverVC"]; // configure using a convenience method (if you have multiple popovers this makes it faster with less code) [self setupPopover:self.popoverVC withSourceView:buttonView.superview // this will be the toolbar sourceRect:self.sourceRect contentSize:contentSize]; [self presentViewController:self.popoverVC animated:YES completion:nil]; }

El método ''setupPopover: withSourceView: sourceRect: contentSize es simplemente un método conveniente para establecer las propiedades popoverPresentationController si planea mostrar múltiples ventanas emergentes y desea que se configuren de la misma manera. Su implementación está abajo.

// convenience method in case you want to display multiple popovers -(void) setupPopover:(UIViewController*)popover withSourceView:(UIView*)sourceView sourceRect:(CGRect)sourceRect contentSize:(CGSize)contentSize { NSLog(@"/npopoverPresentationController: %@/n", popover.popoverPresentationController); popover.modalPresentationStyle = UIModalPresentationPopover; popover.popoverPresentationController.delegate = self; popover.popoverPresentationController.sourceView = sourceView; popover.popoverPresentationController.sourceRect = sourceRect; popover.preferredContentSize = contentSize; popover.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionDown; popover.popoverPresentationController.backgroundColor = [UIColor whiteColor]; }

Para iOS 8 y hasta el viewWillTransitionToSize: withTransitionCoordinator se llama en el controlador de la vista cuando el dispositivo gira.

Implementé este método en mi clase de controlador de vista de presentación como se muestra a continuación.

// called when rotating a device - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { NSLog(@"viewWillTransitionToSize [%@]", NSStringFromCGSize(size)); // resizes popover to new size and arrow location on orientation change [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { if (self.popoverVC) { // get the new frame of our button (this is our new source rect) CGRect viewframe = self.activePopoverBtn ? self.activePopoverBtn.frame : CGRectZero; // update our popover view controller''s sourceRect so the arrow will be pointed in the right place self.popoverVC.popoverPresentationController.sourceRect = viewframe; // update the preferred content size if we want to adapt the size of the popover to fit the new bounds self.popoverVC.preferredContentSize = CGSizeMake(self.view.bounds.size.width -20, self.view.bounds.size.height - 100); } } completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { // anything you want to do when the transition completes }]; }


Puede hacer esto en el método didRotateFromInterfaceOrientation: del controlador de vista que utilizó para presentar la ventana emergente.

Use setPopoverContentSize:animated: método para configurar el tamaño de la ventana emergente.


Swift 3:

class MyClass: UIViewController, UIPopoverPresentationControllerDelegate { ... var popover:UIPopoverPresentationController? ... // Where you want to set the popover... popover = YourViewController?.popoverPresentationController popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) popover?.delegate = self ... // override didRotate... override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) } }


Tengo popoverPresentationController que presento en una vista que tiene una barra de navegación "falsa". Entonces no puedo adjuntar el popoverPresentationController a un barButtonItem. Mi ventana emergente aparece en el lugar correcto, pero no lo hace cuando la pantalla gira.

Entonces, por alguna razón, popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>) no me llama la popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>) .

Para solucionar este problema (iOS 12, Swift 4.2), agregué restricciones a la ventana emergente en el cierre de finalización cuando llamé al presente. Ahora mi popup se queda donde lo esperaría también.

present(viewController, animated: true) { [weak self] in DDLogDebug(String(describing: viewController.view.frame)) if let containerView = viewController.popoverPresentationController?.containerView, let presentedView = viewController.popoverPresentationController?.presentedView, let imageView = self?.headerView.settingsButton { withExtendedLifetime(self) { let deltaY:CGFloat = presentedView.frame.origin.y - imageView.frame.maxY let topConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .top, relatedBy: .equal, toItem: imageView.imageView, attribute: .bottom, multiplier: 1, constant: deltaY) topConstraint?.priority = UILayoutPriority(rawValue: 999) topConstraint?.isActive = true let heightContraint = NSLayoutConstraint.init(item: presentedView, attribute: .height, relatedBy: .equal, toItem: containerView, attribute: .height, multiplier: 0.75, constant: -deltaY) heightContraint?.isActive = true let leftConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .left, relatedBy: .equal, toItem: containerView, attribute: .left, multiplier: 1, constant: presentedView.frame.origin.x) leftConstraint.isActive = true let widthConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: presentedView.frame.width) widthConstraint.isActive = true presentedView.translatesAutoresizingMaskIntoConstraints = false } } }


Tengo un problema similar que resuelvo por este

[myPop presentPopoverFromRect:myfield.frame inView:myscrollview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

Donde myfield es el marco desde el que desea mostrar su myscrollview y myscrollview es la vista de contenedor en la que agrega su ventana emergente como subvista (en mi caso es mi vista de desplazamiento, en lugar de poner inView:self.view Utilizo inView:myscrollview ).


Tuve un mismo problema. En lugar de realizar -presentPopoverFromRect cada vez que se realiza un seguimiento del rectángulo / vista de origen desde el que se presenta, subclasifico UIPopoverController . Después de hacerlo, todo lo que tiene que hacer es configurar UIBarButtonItem / UIView desde donde se debe mostrar la ventana emergente. Incluso puede optar por mostrar la ventana emergente desde un marco personalizado que se puede pasar como un valor NSString.

CSPopoverController.h :

#import <UIKit/UIKit.h> // The original popover controller would not re-orientate itself when the orientation change occurs. To tackle that issue, this subclass is created @interface CSPopoverController : UIPopoverController @property (nonatomic, strong) NSString *popoverDisplaySourceFrame; // Mutually Exclusive. If you want to set custom rect as source, make sure that popOverDisplaySource is nil @property (nonatomic, strong) id popoverDisplaySource; // Mutually exclusive. If UIBarButtonItem is set to it, popoverDisplaySourceFrame is neglected. @property (nonatomic, strong) UIView *popoverDisplayView; @property (nonatomic, assign, getter = shouldAutomaticallyReorientate) BOOL automaticallyReorientate; -(void)reorientatePopover; @end

CSPopoverController.m :

#import "CSPopoverController.h" @implementation CSPopoverController @synthesize popoverDisplaySourceFrame = popoverDisplaySourceFrame_; -(NSString*)popoverDisplaySourceFrame { if (nil==popoverDisplaySourceFrame_) { if (nil!=self.popoverDisplaySource) { if ([self.popoverDisplaySource isKindOfClass:[UIView class]]) { UIView *viewSource = (UIView*)self.popoverDisplaySource; [self setPopoverDisplaySourceFrame:NSStringFromCGRect(viewSource.frame)]; } } } return popoverDisplaySourceFrame_; } -(void)setPopoverDisplaySourceFrame:(NSString *)inPopoverDisplaySourceFrame { if (inPopoverDisplaySourceFrame!=popoverDisplaySourceFrame_) { popoverDisplaySourceFrame_ = inPopoverDisplaySourceFrame; [self reorientatePopover]; } } @synthesize popoverDisplaySource = popoverDisplaySource_; -(void)setPopoverDisplaySource:(id)inPopoverDisplaySource { if (inPopoverDisplaySource!=popoverDisplaySource_) { [self unlistenForFrameChangeInView:popoverDisplaySource_]; popoverDisplaySource_ = inPopoverDisplaySource; [self reorientatePopover]; if ([popoverDisplaySource_ isKindOfClass:[UIView class]]) { UIView *viewSource = (UIView*)popoverDisplaySource_; [self setPopoverDisplaySourceFrame:NSStringFromCGRect(viewSource.frame)]; } if (self.shouldAutomaticallyReorientate) { [self listenForFrameChangeInView:popoverDisplaySource_]; } } } @synthesize popoverDisplayView = popoverDisplayView_; -(void)setPopoverDisplayView:(UIView *)inPopoverDisplayView { if (inPopoverDisplayView!=popoverDisplayView_) { popoverDisplayView_ = inPopoverDisplayView; [self reorientatePopover]; } } @synthesize automaticallyReorientate = automaticallyReorientate_; -(void)setAutomaticallyReorientate:(BOOL)inAutomaticallyReorientate { if (inAutomaticallyReorientate!=automaticallyReorientate_) { automaticallyReorientate_ = inAutomaticallyReorientate; if (automaticallyReorientate_) { [self listenForAutorotation]; [self listenForFrameChangeInView:self.popoverDisplaySource]; } else { [self unlistenForAutorotation]; [self unlistenForFrameChangeInView:self.popoverDisplaySource]; } } } -(void)listenForAutorotation { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil]; } -(void)unlistenForAutorotation { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; } -(void)listenForFrameChangeInView:(id)inView { // Let''s listen for changes in the view''s frame and adjust the popover even if the frame is updated if ([inView isKindOfClass:[UIView class]]) { UIView *viewToObserve = (UIView*)inView; [viewToObserve addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil]; } } -(void)unlistenForFrameChangeInView:(id)inView { if ([inView isKindOfClass:[UIView class]]) { UIView *viewToObserve = (UIView*)inView; [viewToObserve removeObserver:self forKeyPath:@"frame"]; } } // TODO: Dealloc is not called, check why? !!! - (void)dealloc { [self unlistenForFrameChangeInView:self.popoverDisplaySource]; [self unlistenForAutorotation]; DEBUGLog(@"dealloc called for CSPopoverController %@", self); } #pragma mark - Designated initializers -(id)initWithContentViewController:(UIViewController *)viewController { self = [super initWithContentViewController:viewController]; if (self) { [self popoverCommonInitializations]; } return self; } -(void)popoverCommonInitializations { [self setAutomaticallyReorientate:YES]; } #pragma mark - Frame -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object==self.popoverDisplaySource) { [self setPopoverDisplaySourceFrame:nil]; [self reorientatePopover]; } } #pragma mark - Orientation -(void)orientationChanged:(NSNotification *)inNotification { [self reorientatePopover]; } -(void)reorientatePopover { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(performReorientatePopover) object:nil]; // if ([self isPopoverVisible]) { [self performSelector:@selector(performReorientatePopover) withObject:nil afterDelay:0.0]; } } -(void)performReorientatePopover { if (self.popoverDisplaySourceFrame && self.popoverDisplayView) { [self presentPopoverFromRect:CGRectFromString(self.popoverDisplaySourceFrame) inView:self.popoverDisplayView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } else if (self.popoverDisplaySource && [self.popoverDisplaySource isKindOfClass:[UIBarButtonItem class]]) { UIBarButtonItem *barButton = (UIBarButtonItem*)self.popoverDisplaySource; [self presentPopoverFromBarButtonItem:barButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } } @end

Uso:

Si es un UIBarButtonItem desde donde lo está presentando:

CSPopoverController *popOverCont = [[CSPopoverController alloc]initWithContentViewController:navCont]; self.popOver = popOverCont; [popOverCont setPopoverDisplaySource:self.settingsButtonItem];

Si es un UIView desde donde está presentando el popover:

CSPopoverController *popOver = [[CSPopoverController alloc] initWithContentViewController:navigation]; self.iPadPopoverController = popOver; [newDateVC setIPadPopoverController:self.iPadPopoverController]; [popOver setPopoverDisplaySource:inButton]; [popOver setPopoverDisplayView:inView];


UIPopoverController quedó en desuso en ios9 a favor de UIPopoverPresentationController introducido en ios8. (Pasé por esta transición también cuando UIActionSheet de UIActionSheet a UIAlertController ). Tiene dos opciones (ejemplo en obj-C):

A. Implementar el método UIViewController continuación (UIKit llama a este método antes de cambiar el tamaño de la vista de un controlador de vista presentado).

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { // Fix up popover placement if necessary, *after* the transition. // Be careful here if a subclass also overrides this method. if (self.presentedViewController) { UIPopoverPresentationController *presentationController = [self.presentedViewController popoverPresentationController]; UIView *selectedView = /** YOUR VIEW */; presentationController.sourceView = selectedView.superview; presentationController.sourceRect = selectedView.frame; } }]; }

B. Como alternativa, cuando configure su UIPopoverPresentationController para presentar, también establezca su delegado. por ejemplo, su presentación vc puede implementar UIPopoverPresentationControllerDelegate y asignarse a sí mismo como delegado. Luego implementa el método delegado:

- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView * _Nonnull *)view { UIView *selectedView = /** YOUR VIEW */; // Update where the arrow pops out of in the view you selected. *view = selectedView; *rect = selectedView.bounds; }