只支持某个页面旋转
最近有人问我一个关于iPhone上旋转的需求:App中只对某个页面支持旋转,其它页面一律只支持 portrait。比如,A push B,在B页面旋转后,页面会自适应;当B处于 landscape 时,pop回到A时,A还要处于 portrait。
一开始,我的答案时重载 - (UIInterfaceOrientationMask)supportedInterfaceOrientations
,对方试了一下没有效果。亲身实践了一下,单设置此方法确实不行。了解到对方的项目中页面并没有完全使用 AutoLayout,但还是实现了 rotation 相关的方法,于是使用在 AppDelegate 中实现:
- (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window
具体步骤是在 AppDelegate 中添加属性 UIInterfaceOrientationMask
,在每个 VC 出现和消失的时候,更新该属性。回到这个例子,可以在 AppDelegate 初始化 UIInterfaceOrientationMask
为 UIInterfaceOrientationMaskPortrait
,接着再在B中更新:
- (void)viewWillAppear:(BOOL)animated{
//rotation support
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[delegate updateSupportedOrientation:UIInterfaceOrientationMaskAllButUpsideDown];
}
- (void)viewWillDisappear:(BOOL)animated{
//rotation support
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[delegate updateSupportedOrientation:UIInterfaceOrientationMaskPortrait];
}
这个方法的原理在于旋转的时候,UIWindow首先接收到相应,然后再传递给 rootViewController 以及子 viewController。而如果实现了协议方法
UIInterfaceOrientationMaskAllButUpsideDown:supportedInterfaceOrientationsForWindow:
那么每次系统检测到旋转事件后,都会调用该方法。
正常的旋转处理流程
iOS8开始,和 rotation 相关的几个方法已经废弃:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation;
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
尤其是第一个,在编译阶段 Xcode 会提示该方法已经废弃。其它3个方法,如果没有使用新的处理rotation的方法时,还会继续执行。
如今,推荐使用的处理 rotation 的方法如下:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator;
使用coordinator可以处理不同方向下的constraints或是页面布局,这里主要注意的是如何正确的判断当前所处的模式。Apple推荐的是使用 UITraitCollection / UITraitEnvironment 来检测当前的方向,下面举个例子:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { context in
self.p_updateContstraints()
}) { contenxt in
}
}
fileprivate func p_updateContstraints() {
let constraints = self.viewRed.constraints
self.viewRed.removeConstraints(constraints)
self.p_removeAnchorConstranits(target: self.viewRed, from: self.view)
let widthConstraint = NSLayoutConstraint(item: self.viewRed!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1.0, constant: 50)
let heightConstraint = NSLayoutConstraint(item: self.viewRed!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1.0, constant: 50)
self.viewRed.addConstraints([widthConstraint, heightConstraint])
let trait = self.traitCollection
let safeLayoutGuide = self.view.safeAreaLayoutGuide
if trait.userInterfaceIdiom == .phone {
if trait.verticalSizeClass == .compact && trait.horizontalSizeClass == .regular{
//plus landscape: left-top
self.viewRed.topAnchor.constraint(equalTo: safeLayoutGuide.topAnchor, constant: 0).isActive = true
self.viewRed.leadingAnchor.constraint(equalTo: safeLayoutGuide.leadingAnchor, constant: 0).isActive = true
} else if trait.verticalSizeClass == .compact && trait.horizontalSizeClass == .compact {
//landscape: left-bottom
self.viewRed.bottomAnchor.constraint(equalTo: safeLayoutGuide.bottomAnchor, constant: 0).isActive = true
self.viewRed.leadingAnchor.constraint(equalTo: safeLayoutGuide.leadingAnchor, constant: 0).isActive = true
} else if trait.verticalSizeClass == .regular && trait.horizontalSizeClass == .compact {
//portrait: right-top
self.viewRed.topAnchor.constraint(equalTo: safeLayoutGuide.topAnchor, constant: 0).isActive = true
self.viewRed.trailingAnchor.constraint(equalTo: safeLayoutGuide.trailingAnchor, constant: 0).isActive = true
}
}else if trait.userInterfaceIdiom == .pad{
}else {
}
}
fileprivate func p_removeAnchorConstranits(target: UIView, from superView: UIView ){
let sConstraints = superView.constraints
var toDeleteConstraints:[NSLayoutConstraint] = []
for constraint in sConstraints {
let firstView = constraint.firstItem as? UIView
let secondView = constraint.secondItem as? UIView
if firstView == target || secondView == target {
toDeleteConstraints.append(constraint)
}
}
superView.removeConstraints(toDeleteConstraints)
}
上述代码主要是在iPhone portrait/landscape和plus landscape模式下,使红色方块处于不同的位置。因为使用了 safearealayoutguide,只对iOS11有效。
Comments