web-dev-qa-db-fra.com

Comment faire en sorte que UIPopoverController conserve la même position après la rotation?

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.

20
B.S.

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.

29
Max MacLeod

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;
}
16
John Stricker

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

6
Markus Rautopuro

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.

5
dead_soldier

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())
        }
    }
2
sabiland

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;
}
2
qix

J'ai un problème similaire que je résous par ceci

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

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

1
mhrrt

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
}
1
Zaraki

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
    }];
}
0
digitalHound

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)
        }

}
0
user5195185

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];
0
Raj Pawan Gumdal

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
                }
            }
        }
0
Rand