官方文档告诉你UIPopoverPresentationController怎么用

何为UIPopoverPresentationController?
An object that manages the display of content in a popover.--一个用来管理在一个弹出窗口中展示内容的对象

先来看一下效果

使用UIPopoverPresentationController的效果

iOS9.0废弃的UIPopoverController

可以通过这篇文章了解一下以前的UIPopoverController使用方法UIPopoverController的使用

转到iOS8.0之后的UIPopoverPresentationController

官方文档对UIPopoverPresentationController的介绍原文为:

From the time a popover is presented until the time it is dismissed, UIKit uses an instance of this class to manage the presentation behavior. You use instances of this class as-is to configure aspects of the popover appearance and behavior for view controllers whose presentation style is set to popover
.

意思是说:从一个popover(也就是弹出的窗口)被presented(呈现)到被dismissed(退出)的期间,UIkit使用UIPopoverPresentationController的实例来管理呈现的行为。我们可以使用这个实例来为那些呈现的样式为popover的控制器设置popover的各个方面的外观和行为

另外官方文档还提到:

In nearly all cases, you use this class as-is and do not create instances of it directly. UIKit creates an instance of this class automatically when you present a view controller using the popover
style. You can retrieve that instance from the presented view controller’s popoverPresentationController
property and use it to configure the popover behavior.

意思是说:在几乎所有的情况下,我们都应该使用这个类并且不要直接创建它的实例。因为当我们present(呈现/弹出)一个控制器使用popover样式的时候,UIKit会自动为我们创建一个这个类的实例。我们可以通过控制器的popoverpresentationcontroller属性来重新获取UIPopoverPresentationController的实例,并且使用它来设置我们的popover的行为

如果当我们presenting一个控制器的时候,我们不想立即配置popover的话,我们可以使用它的代理去设置它。在present控制器的过程当中,UIPopoverPresentationController实例会调用遵守UIPopoverPresentationControllerDelegate的对象的很多方法,来询问一些信息和告诉它关于它应该呈现的状态

官方提供了如下的使用Swift代码的例子(我从Swfit切换到Objective-C,但还是显示的Swfit的例子😅,感觉苹果要默默干掉OC)

@IBAction func displayOptionsForSelectedItem () {
   let storyboard = UIStoryboard(name: "Main", bundle: nil)
   let optionsVC = storyboard.instantiateViewController(withIdentifier: "itemOptionsViewController")   
   optionsVC.modalPresentationStyle = .popover
   optionsVC.popoverPresentationController?.barButtonItem = optionsControl
   self.present(optionsVC, animated: true) {}
}

官方提供的示例代码就是简单的把设置了一下将要弹出的控制器的modalPresentationStyle和popoverPresentationController用来显示在什么位置。但是使用上述代码运行的话,我们会发现它竟然把要弹出的控制器全屏展示了(这可能不是我们想要的效果)

定制我们想要的popopver效果(介绍)

Customizing the Popover Behavior(定制popover的行为)
  • delegate : UIPopoverPresentationControllerDelegate?
    使用控制器的popoverPresentationController的delegate属性定制我们想要的效果
Configuring the Popover Appearance(设置popover的外观)
  • popoverLayoutMargins : UIEdgeInsets
    定义popover允许展示的区域的各部分间隙。
    客户端可能想要改变展示的popover的可用范围。默认的是返回距离展示的popover的各边的10个点,并且弹出的popover总是占据状态栏(我试了一下点击状态栏,popover不会消失),矩形的inset总是以当前设备的朝向来表示,(0,0)一直在设备的左上角。这可能需要在设备的旋转上进行改变。我设置了一下这个属性,即使在选择的情况下,好像没看到什么改变
  • backgroundColor: UIColor?
    popover的背景视图颜色
  • passthroughViews : [UIView]?
    这个属性里可以放置那些当popover显示的时候的可和用户交互的视图
  • popoverBackgroundViewClass : UIPopoverBackgroundViewMethods.Type?
    这个属性可以是UIPopoverBackgroundView的子类,也可以自定义一个UIView但是必须遵守UIPopoverBackgroundViewMethods协议并实现这个协议的响应方法。这个协议可以设置箭头的位置、高度以及它的contentView的inset
  • canOverlapSourceViewRect : Bool
    表示popover是否可以和它的源视图矩形重叠,默认是false。如果为true的话,表示popover的内容比可用空间更大时被允许重叠源视图以容纳内容
Specifying the Popover’s Anchor Point(指定popover的锚点)

锚点也就是popover的箭头所指向的位置

  • barButtonItem: UIBarButtonItem?
    指定popover指向UIBarButtonItem
  • sourceView : UIView?
    指定popover指向的View
  • sourceRect : CGRect
    指定popover指向的矩形
Configuring the Popover Arrows(设置popover的箭头)
  • permittedArrowDirections: UIPopoverArrowDirection
    表示允许的方向
  • arrowDirection: UIPopoverArrowDirection
    获取箭头的指向。在弹出之前,这个值是UIPopoverArrowDirectionUnknown

UIPresentationController

UIPopoverPresentationController继承自UIPresentationController,这个对象的作用官方文档说是:一个为弹出的视图控制器提供高级视图和转场管理的对象。对于UIPresentationController更相近的内容限于篇幅,不再介绍,可以查看文档UIPresentationController

定制我们想要的popopver效果(实现)

先来说一下,如果我们像上文官方提供的例子的话,运行的结果将是全屏,这可能是系统会帮我们自适应弹出的样式,原因如下:

  • UIPopoverPresentationController继承自UIPresentationController,在源代码里,可以看到这个类包含一个属性
// By default this implementation defers to the delegate, if one exists, or returns the current presentation style. UIFormSheetPresentationController, and
// UIPopoverPresentationController override this implementation to return UIModalPresentationStyleFullscreen if the delegate does not provide an
// implementation for adaptivePresentationStyleForPresentationController:

open var adaptivePresentationStyle: UIModalPresentationStyle { get }

通过类型可以看出这个属性就是设置弹出popover的样式,再看一下注释,发现默认是根据UIAdaptivePresentationControllerDelegate实现的,如果实现了自适应样式的方法,那么这个属性的值就是设为响应的值。如果它的协议没有在adaptivePresentationStyleForPresentationController方法中实现的话,在UIFormSheetPresentationController和UIPopoverPresentationController重写了这个实现,并且返回UIModalPresentationStyleFullscreen。这就说明了,为什么官方的例子弹出的效果是全屏的了

  • UIPopoverPresentationControllerdelegate遵守的协议为UIPopoverPresentationControllerDelegate,这个协议又继承自UIAdaptivePresentationControllerDelegate。在Xcode中查看这个协议的内容,会发现这个协议可以控制自适应弹出的样式。并且从下图的第一个方法的注释中可以看出,自适应的样式只支持UIModalPresentationFullScreenUIModalPresentationOverFullScreen,并没有我们想要的popover
UIAdaptivePresentationControllerDelegate

另外,我们注意到第二个方法的注释:返回UIModalPresentationNone指示不会发生自适应的样式,经过测试,果真如此。如果在这个代理方法返回UIModalPresentationNone,我们将可以定制我们自己的弹出样式,这也是本文所展示的效果的解决方法。同样,如果在第一个代理返回返回UIModalPresentationNone的话,也可以禁止弹出的自适应效果

了解了思路以后,用代码来写出我们想要的效果

  • Swift实现

    • 基于UIBarButtonItem
    let vc: UIViewController = UIViewController()
    @IBAction func itemPop(_ sender: UIBarButtonItem) {
      vc.view.backgroundColor = UIColor.orange
      // 设置弹出的尺寸
      vc.preferredContentSize = CGSize(width: 150, height: 150)
      // 设置弹出的样式
      vc.modalPresentationStyle = UIModalPresentationStyle.popover
    
      // 这个属性为true时表明除了popover内部,其它地方不响应事件,也就是说点击其他地方popover不会消失
      // 这个属性默认为false,也就是点击popover周围的地方会消失(状态栏除外)
      // modalInPopover is set on the view controller when you wish to force the popover hosting the view controller into modal behavior. 
      // When this is active, the popover will ignore events outside of its bounds until this is set to NO.
      vc.isModalInPopover = true
    
      // 获取UIPopoverPresentationController对象
      let popoverPresentationController = vc.popoverPresentationController
      // 设置popoverPresentationController显示的锚点
      popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
      // 设置代理
      popoverPresentationController?.delegate = self
      // 设置方向
      popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.any
    
      self.present(vc, animated: true, completion: nil)
    }
    
    // 方法一:
    func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
      return .none
    }
    
    // 方法二:
    //    func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
    //        return .none
    //    }
    

    效果图如本文开篇图片效果

    • 基于父类是UIView的控件
    let vc: UIViewController = UIViewController()
    @IBAction func buttonPop(_ btn : UIButton) {
      vc.preferredContentSize = CGSize(width: 150, height: 150)
      vc.modalPresentationStyle = UIModalPresentationStyle.popover
      
      let popoverPresentationController = vc.popoverPresentationController
      popoverPresentationController?.sourceView = btn
      popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
      popoverPresentationController?.delegate = self
      popoverPresentationController?.backgroundColor = UIColor.purple
    
      self.present(vc, animated: true, completion: nil)
    } 
    // 方法一:
    func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
      return .none
    }
    // 方法二:
    //    func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
    //        return .none
    //    }
    

效果图如下

基于父类是UIView的控件1

我们发现并不是我们想要的效果,popover的位置好像不对。调整它的位置,这时候就要用到sourceRect属性了。没有设置的情况下默认都是0,所以显示在了按钮的左上角(0,0,0,0)的位置,下面设置一下sourceRect属性

popoverPresentationController?.sourceRect = CGRect(x: btn.frame.size.width / 2, y: btn.frame.size.height, width: 0, height: 0)

效果图如下

基于父类是UIView的控件2
  • OC实现

    • 基于UIBarButtonItem
    - (IBAction)itemPop:(id)item {
      _vc.view.backgroundColor = [UIColor orangeColor];
      _vc.preferredContentSize = CGSizeMake(150, 150);
      
      _vc.modalPresentationStyle = UIModalPresentationPopover;
    
      UIPopoverPresentationController *popoverPresentationController = _vc.popoverPresentationController;
      popoverPresentationController.barButtonItem = item;
      popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
      popoverPresentationController.delegate = self;
      [self presentViewController:_vc animated:YES completion:nil];
    }
    
    #pragma mark - UIPopoverPresentationControllerDelegate
    - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
      return UIModalPresentationNone;
    }
    
    //- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
    //    return UIModalPresentationNone;
    //}
    

    效果和前文一样

    • 基于父类是UIView的控件
    - (IBAction)popBtnClick:(UIButton *)btn {
      _vc.preferredContentSize = CGSizeMake(150, 150);
      _vc.modalPresentationStyle = UIModalPresentationPopover;
      
      UIPopoverPresentationController *popoverPresentationController = _vc.popoverPresentationController;
      popoverPresentationController.sourceView = btn;
      popoverPresentationController.sourceRect = CGRectMake(btn.frame.size.width / 2, btn.frame.size.height, 0, 0);
      popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;
      popoverPresentationController.delegate = self;
      popoverPresentationController.backgroundColor = [UIColor purpleColor];
      
      [self presentViewController:_vc animated:YES completion:nil]; 
     }
    
    #pragma mark - UIPopoverPresentationControllerDelegate
    - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
      return UIModalPresentationNone;
    }
    
    //- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
    //    return UIModalPresentationNone;
    //}
    

遗留问题:当第一次点击阴影使popover消失的时候,会报一个警告,可以点击这里进行了解

[Warning] <_UIPopoverBackgroundVisualEffectView 0x7f99f4607860> is being asked to animate its opacity. This will cause the effect to appear broken until opacity returns to 1.

参考文献:
UIPopoverPresentationController
UIPopoverController的使用

如果不想用系统的样式,也可以自定义,通过使用popoverBackgroundViewClass这个属性来自定义背景,具体可以参考这篇文章Customizing UIPopover with UIPopoverBackgroundView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容