回転後、画面上の同じ位置にポップオーバーを維持できません。フレームをポップオーバーに設定するだけでは、回転後にひどい動作をするため、これを行う良い方法はありますか。popover.frame = CGRectMake(someFrame);
回転後、ポップオーバーは画面の中央にある場合にのみ正常に表示されます。
Appleはまさにこの問題に関するQ&Aを持っています。あなたはここで詳細を見つけることができます:
技術的なQ&A QA1694方向変更中のポップオーバーコントローラーの処理
基本的に、このテクニックでは、ViewControllerのdidRotateFromInterfaceOrientation
メソッドで、次のようにポップオーバーを再度表示することを説明しています。
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[aPopover presentPopoverFromRect:targetRect.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
詳細については、上記の記事を読んでください。また、 IPopoverControllerクラスリファレンス :
ポップオーバーが表示されているときにユーザーがデバイスを回転させると、ポップオーバーコントローラーはポップオーバーを非表示にし、回転の最後に再び表示します。ポップオーバーコントローラーは、ポップオーバーを適切に配置しようとしますが、場合によっては、ポップオーバーを再度表示したり、完全に非表示にしたりする必要があります。たとえば、バーボタンアイテムから表示される場合、ポップオーバーコントローラは、バーボタンアイテムの位置の変更を考慮して、ポップオーバーの位置(および場合によってはサイズ)を自動的に調整します。ただし、回転中にバーボタンアイテムを削除した場合、またはビューのターゲット長方形からポップオーバーを表示した場合、ポップオーバーコントローラはポップオーバーの位置を変更しようとしません。そのような場合は、ポップオーバーを手動で非表示にするか、適切な新しい位置から再度表示する必要があります。これは、ポップオーバーの表示に使用したビューコントローラーのdidRotateFromInterfaceOrientation:メソッドで実行できます。
IOS 8.0.2以降、willRotateToInterfaceOrientation 効果はありません。 mhrrtが述べたように、デリゲートメソッドを使用する必要があります。
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
したがって、たとえば、押されたボタンのすぐ下にポップオーバーを表示する場合は、次のコードを使用します。
- (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;
}
IOS 7では、- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
を使用して、インターフェイスの向きの変更に関するUIPopoverControllerのビューの位置を変更できます。
UIPopoverControllerDelegate
ドキュメント を参照してください。
これは、ポップオーバーを表示するために使用したViewControllerのdidRotateFromInterfaceOrientation:
メソッドで実行できます。
ポップオーバーのサイズを設定するには、setPopoverContentSize:animated:
メソッドを使用します。
UIPopoverController
はios9で非推奨になり、 IPopoverPresentationController ios8で導入されました。 (UIActionSheet
からUIAlertController
に移行するときにも、この移行を行いました。)2つの選択肢があります(obj-Cの例)。
A.以下のUIViewController
メソッドを実装します(UIKitは、提示されたView Controllerのビューのサイズを変更する前にこのメソッドを呼び出します)。
- (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.または、UIPopoverPresentationController
を表示するように構成する場合は、そのデリゲートも設定します。例えば提示するvcはUIPopoverPresentationControllerDelegate
を実装し、それ自体をデリゲートとして割り当てることができます。次に、デリゲートメソッドを実装します。
- (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;
}
新しいrect(rect.initialize(...))を設定しようとしましたが、機能します。
func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer<CGRect>, inView view: AutoreleasingUnsafeMutablePointer<UIView?>) {
if popoverPresentationController.presentedViewController.view.tag == Globals.PopoverTempTag
{
rect.initialize(getForPopupSourceRect())
}
}
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
}
PopOverコントローラーを初期化します
var popoverContent: PopoverContentViewController?
PopOverコントローラーの定義を書き込む
popoverContent = self.storyboard?.instantiateViewController(withIdentifier: "PopoverContentViewController") as? PopoverContentViewController
popoverContent?.modalPresentationStyle = .popover
let popover = popoverContent?.popoverPresentationController!
popover?.delegate = self
popoverContent?.preQuestionInfoPopUpViewDelegateObject = self
popover?.permittedArrowDirections = UIPopoverArrowDirection()
popover?.sourceView = self.view
popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 330, height: 330)
現在のPopOverコントローラー
self.present(popoverContent、animated:true、completion:nil)
以下のメソッドを記述し、ポップオーバーに新しいサイズを割り当てます。
func viewWillTransition(to size:CGSize、with coordinator:UIViewControllerTransitionCoordinator){let popover = popoverContent?.popoverPresentationController! popover?.sourceRect = CGRect(x:size.width/2、y:size.height/2、width:0、height:0)}
私はこれによって解決する同様の問題を抱えています
[myPop presentPopoverFromRect:myfield.frame inView:myscrollview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
ここで、myfield
はポップオーバーを表示するフレームであり、myscrollview
はポップオーバーをサブビューとして追加するコンテナビューです(私の場合は、inView:self.view
を配置する代わりに私のスクロールビューです。inView:myscrollview
を使用します)。
IOSの場合> 8 John Strickersの回答は役に立ちましたが、私が望んでいたことをしませんでした。
これが私のために働いた解決策です。 (完全なサンプルプロジェクトをダウンロードしたい場合は、ここにあります: 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がViewControllerで呼び出されます。
以下に示すように、このメソッドを現在のViewControllerクラスに実装しました。
// 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
}];
}
スウィフト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)
}
}
私も同じ問題を抱えていました。表示元のソース長方形/ビューを追跡して毎回-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];
「偽の」ナビゲーションバーがあるビューに表示するpopoverPresentationControllerがあります。そのため、popoverPresentationControllerをbarButtonItemにアタッチできません。ポップアップは正しい場所に表示されますが、画面が回転しても表示されません。
したがって、何らかの理由でpopoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>)
が呼び出されません。
これを回避するために(iOS 12、Swift 4.2)presentを呼び出すときに、完了クロージャのポップアップに制約を追加しました。これで、ポップアップも期待どおりの場所に留まります。
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
}
}
}