iOS显示ViewController
的方式有两种:Push
和Modal
。当以Modal
方式显示ViewController
的时候,UIModalPresentationStyle
属性决定了模态显示样式,定义如下:
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) {
UIModalPresentationFullScreen = 0,
UIModalPresentationPageSheet,
UIModalPresentationFormSheet,
UIModalPresentationCurrentContext,
UIModalPresentationCustom,
UIModalPresentationOverFullScreen,
UIModalPresentationOverCurrentContext,
UIModalPresentationPopover,
UIModalPresentationNone = -1,
};
以下内容将对上述各样式进行详细讲解:
UIModalPresentationFullScreen
覆盖全屏,并以RootViewController
为Context
进行显示。当显示完成后,会将属于PresentingViewController
的所有视图暂时移出视图栈。
UIModalPresentationOverFullScreen
同上,但与UIModalPresentationFullScreen
不同的是,当显示完成后,不会将属于PresentingViewController
的视图移出视图栈。因此,如果该PresentedViewController
的内容视图拥有透明度的话,可以透视到位于PresentingViewController
的视图内容。比较以下两张视图层次结构图可以发现,当以Full Screen
方式显示ViewController
时,CurrentContextViewController
及其所有子视图被移出了视图栈,但当以Over Full Screen
方式显示ViewController
时,CurrentContextViewController
及其所有子视图并未被移出视图栈。
UIModalPresentationCurrentContext
UIKit
将自当前视图节点向上搜索视图控制器层次结构,并以第一个definesPresentationContext
属性为true
的ViewController
为Context
进行显示。当显示完成后,会将属于PresentingViewController
的所有视图暂时移出视图栈。
UIModalPresentationOverCurrentContext
同上,但与UIModalPresentationCurrentContext
不同的是,当显示完成后,不会将属于PresentingViewController
的视图移出视图栈。
UIModalPresentationPageSheet
如果UIWindow
的horizontalSizeClass
为compact
时,显示效果同UIModalPresentationFullScreen
。
如果UIWindow
的horizontalSizeClass
为regular
时,显示如下:
UIModalPresentationFormSheet
如果UIWindow
的horizontalSizeClass
为compact
时,显示效果同UIModalPresentationFullScreen
。
如果UIWindow
的horizontalSizeClass
为regular
时,显示如下:
对于
Form Sheet
与Page Sheet
,如何当UIWindow
的horizontalSizeClass
为compact
时,仍然显示为Sheet
样式而非Full Screen
?只需实现UIAdaptivePresentationControllerDelegate
代理,并设置被显示ViewController
的presentationController.delegate
即可。
extension CustomViewController: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
UIModalPresentationCustom
在使用此样式呈现ViewController
之前,需要实现UIViewControllerTransitioningDelegate
协议,并将ViewController
的transitioningDelegate
属性设置为上述的自定义委托。
class CustomViewController: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.modalPresentationStyle = .custom
self.transitioningDelegate = self
}
}
extension CustomViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return CustomPresentationController(presentedViewController: presented, presenting: presenting)
}
}
class CustomPresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
let bounds = containerView!.bounds
let size = CGSize(width: 300, height: 500)
let origin = CGPoint(x: bounds.midX - size.width / 2, y: bounds.midY - size.height / 2)
return CGRect(origin: origin, size: size)
}
override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}
}
UIModalPresentationPopover
如果UIWindow
的horizontalSizeClass
为compact
时,显示效果同UIModalPresentationFullScreen
。
如果UIWindow
的horizontalSizeClass
为regular
时,显示如下:
class CustomViewController: UIViewController {
@IBAction func presentPopover(_ sender: Any) {
let vc = UIViewController()
vc.view.backgroundColor = UIColor.red
vc.modalPresentationStyle = .popover
vc.preferredContentSize = CGSize(width: 200, height: 50)
if let popoverPresentationController = vc.popoverPresentationController {
let sourceView = sender as! UIButton
popoverPresentationController.sourceView = sourceView
popoverPresentationController.sourceRect = sourceView.bounds
popoverPresentationController.delegate = self
}
self.present(vc, animated: true)
}
}
extension CustomViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
// 如果当UIWindow的horizontalSizeClass为compact时,也想显示为Popover而非FullScreen,只需实现本方法并返回none即可。
return .none
}
}