Je ne peux pas garder la même position sur l'écran après la rotation. Existe-t-il un bon moyen de le faire, car le simple fait de définir une image à afficher s’affiche très mal après une rotation .popover.frame = CGRectMake(someFrame);
Après la rotation, l’affichage semble bien si elle est au centre de l’écran.
Apple a un Q & A sur exactement cette question. Vous pouvez trouver les détails ici:
Q & R technique QA1694 Gestion des contrôleurs Popover lors des changements d’orientation
En gros, la technique explique que, dans la méthode didRotateFromInterfaceOrientation
de votre contrôleur de vue, vous présenterez à nouveau le pop-up de la manière suivante:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[aPopover presentPopoverFromRect:targetRect.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
Pour plus d'informations, lisez l'article ci-dessus, ainsi que la référence de la classe UIPopoverController :
Si l'utilisateur fait pivoter le périphérique alors qu'un popover est visible, le contrôleur de popover masque le popover, puis l'affiche à nouveau à la fin de la rotation. Le contrôleur popover tente de positionner correctement le popover, mais vous devrez peut-être le présenter à nouveau ou le cacher complètement dans certains cas. Par exemple, lorsqu'il est affiché à partir d'un élément de bouton de barre, le contrôleur de popover ajuste automatiquement la position (et éventuellement la taille) du popover afin de prendre en compte les modifications apportées à la position de l'élément de bouton de barre. Toutefois, si vous supprimez l'élément de bouton à barres au cours de la rotation ou si vous avez présenté le survol à partir d'un rectangle cible dans une vue, le contrôleur de survol ne tente pas de repositionner le survol. Dans ces cas, vous devez masquer manuellement le popover ou le présenter à nouveau à partir d'un nouvel emplacement approprié. Vous pouvez le faire dans la méthode didRotateFromInterfaceOrientation: du contrôleur de vue que vous avez utilisée pour présenter le popover.
Depuis iOS 8.0.2 willRotateToInterfaceOrientation n'aura aucun effet . Comme mentionné par mhrrt, vous devez utiliser la méthode delegate:
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
Ainsi, par exemple, si vous souhaitez que votre popover apparaisse directement sous un bouton sur lequel vous avez appuyé, utilisez le code suivant:
- (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;
}
Dans iOS 7, vous pouvez utiliser - (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
pour repositionner la vue de votre UIPopoverController sur le changement d'orientation de l'interface.
Voir la UIPopoverControllerDelegate
documentation .
Vous pouvez le faire dans la méthode didRotateFromInterfaceOrientation:
du contrôleur de vue que vous avez utilisé pour présenter le popover.
Utilisez la méthode setPopoverContentSize:animated:
pour définir la taille du popover.
J'ai juste essayé de définir new rect (rect.initialize (...)) et cela fonctionne.
func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer<CGRect>, inView view: AutoreleasingUnsafeMutablePointer<UIView?>) {
if popoverPresentationController.presentedViewController.view.tag == Globals.PopoverTempTag
{
rect.initialize(getForPopupSourceRect())
}
}
UIPopoverController
a été déconseillé dans iOS 9 au profit de UIPopoverPresentationController introduit dans iOS 8. (J'ai également traversé cette transition en passant de UIActionSheet
à UIAlertController
.) Vous avez deux choix (exemple dans obj-C):
A. Implémentez la méthode UIViewController
ci-dessous (UIKit appelle cette méthode avant de modifier la taille de la vue d’un contrôleur de vue présenté).
- (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. Sinon, lors de la configuration de votre UIPopoverPresentationController
à présenter, définissez également son délégué. par exemple. votre vc présentateur peut implémenter UIPopoverPresentationControllerDelegate
et s’assigner lui-même en tant que délégué. Puis implémentez la méthode delegate:
- (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;
}
J'ai un problème similaire que je résous par ceci
[myPop presentPopoverFromRect:myfield.frame inView:myscrollview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
Où myfield
est l'image à partir de laquelle vous voulez afficher votre popover et myscrollview
est la vue conteneur dans laquelle vous ajoutez votre popover en tant que sous-vue (dans mon cas, c'est mon scrollview, au lieu de mettre inView:self.view
j'utilise inView:myscrollview
).
Pour 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
}
Pour iOS> 8, John Strickers a aidé, mais n'a pas fait ce que je voulais.
Voici la solution qui a fonctionné pour moi. (Si vous souhaitez télécharger un exemple de projet complet, cliquez ici: https://github.com/appteur/uipopoverExample )
J'ai créé une propriété pour contenir tout popover que je voulais présenter et j'ai également ajouté une propriété pour suivre le sourceRect et un autre pour l'affichage du bouton sur lequel je voulais que la flèche de popover pointe.
@property (nonatomic, weak) UIView *activePopoverBtn;
@property (nonatomic, strong) PopoverViewController *popoverVC;
@property (nonatomic, assign) CGRect sourceRect;
Le bouton qui a déclenché mon popover est dans une barre UIToolbar. Lorsque vous appuyez dessus, il exécute la méthode suivante qui crée et lance le popover.
-(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];
}
La méthode 'setupPopover: withSourceView: sourceRect: contentSize est simplement une méthode pratique pour définir les propriétés popoverPresentationController si vous prévoyez d'afficher plusieurs popovers et de les configurer de la même manière. Sa mise en œuvre est ci-dessous.
// 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];
}
Pour iOS 8 et versions supérieures, viewWillTransitionToSize: withTransitionCoordinator est appelée sur le contrôleur de vue lorsque le périphérique pivote.
J'ai implémenté cette méthode dans ma classe de contrôleur de présentation, comme indiqué ci-dessous.
// 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
}];
}
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)
}
}
J'ai eu le même problème. Au lieu d'effectuer -presentPopoverFromRect
à chaque fois en gardant une trace du rectangle/de la vue source à partir duquel il est présenté, j'ai sous-classé UIPopoverController
. Après cela, tout ce que vous avez à faire est de définir soit le UIBarButtonItem/UIView à partir duquel le popover doit être affiché. Vous pouvez même choisir d'afficher le popover à partir d'un cadre personnalisé pouvant être transmis en tant que valeur 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
Utilisation:
S'il s'agit d'un UIBarButtonItem d'où vous le présentez:
CSPopoverController *popOverCont = [[CSPopoverController alloc]initWithContentViewController:navCont];
self.popOver = popOverCont;
[popOverCont setPopoverDisplaySource:self.settingsButtonItem];
S'il s'agit d'un UIView à partir duquel vous présentez le popover:
CSPopoverController *popOver = [[CSPopoverController alloc] initWithContentViewController:navigation];
self.iPadPopoverController = popOver;
[newDateVC setIPadPopoverController:self.iPadPopoverController];
[popOver setPopoverDisplaySource:inButton];
[popOver setPopoverDisplayView:inView];
J'ai popoverPresentationController que je présente dans une vue dotée d'une "fausse" barre de navigation. Donc, je ne peux pas attacher le popoverPresentationController à un barButtonItem. Ma fenêtre contextuelle apparaît au bon endroit, mais pas lorsque l'écran pivote.
Donc, pour une raison quelconque, popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>)
ne se fait pas appeler pour moi.
Pour contourner ce problème (iOS 12, Swift 4.2), j’ai ajouté des contraintes à la fenêtre contextuelle lors de la fermeture de l’achèvement lors de l’appel en cours. Maintenant, mon popup reste là où je l’attendais aussi.
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
}
}
}