Как заставить UIPopoverController удерживать ту же позицию после вращения? - PullRequest
21 голосов
/ 30 января 2012

Я не могу держать поповер на той же позиции на экране после поворота. Есть ли какой-нибудь хороший способ сделать это, потому что просто установка некоторого кадра на поповер работает ужасно после поворота. popover.frame = CGRectMake(someFrame); После поворота поповер выглядит хорошо, только если он находится в центре экрана.

Ответы [ 13 ]

0 голосов
/ 21 мая 2017

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 голосов
/ 20 ноября 2015

Для iOS> 8 Ответ Джона Стрикерса помог, но не сделал то, что я хотел, чтобы он сделал.

Вот решение, которое сработало для меня.(Если вы хотите загрузить полный пример проекта, он находится здесь: https://github.com/appteur/uipopoverExample)

Я создал свойство для хранения любого поповера, который я хотел представить, а также добавил свойство для отслеживания sourceRect и другое для представленияКнопка Я хотел, чтобы стрелка поповера указывала на.

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

Кнопка, которая вызвала мой поповер, находится на панели инструментов UIToolbar. При нажатии она запускает следующий метод, который создает и запускает поповер.

-(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];

}

Метод 'setupPopover: withSourceView: sourceRect: contentSize' - это просто удобный метод для установки свойств popoverPresentationController, если вы планируете отображать несколько всплывающих окон и хотите, чтобы они были настроены одинаково. Его реализация приведена ниже.

// 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];
}

Для iOS 8 и выше viewWillTransitionToSize: withTransitionCoordinator get вызывается на контроллере представления при вращении устройства.

Я реализовал этот метод в своем представлении класса контроллера представления, как показано ниже.

// 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 голосов
/ 21 октября 2013

У меня была такая же проблема.Вместо выполнения -presentPopoverFromRect каждый раз, отслеживая исходный прямоугольник / вид, из которого он представлен, я подклассифицировал UIPopoverController.После этого все, что вам нужно сделать, это установить либо UIBarButtonItem / UIView, из которого должен отображаться всплывающее окно.Вы даже можете выбрать отображение всплывающего окна из пользовательского кадра, который может быть передан в виде значения 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

Использование:

Если это UIBarButtonItem, из которого вы его представляете:

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

Если это UIView, из которого вы представляете поповер:

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