在 iOS 开发中,屏幕旋转与多窗口适配是高频且极易踩坑的核心能力:App 全局竖屏、单个页面强制横屏、播放器手动旋转、iPad 分屏(Split View)、浮窗(Floating)、多窗口拖拽。
日常开发几乎所有人都遇到过这些无解问题:
项目全局锁竖屏,播放器页面无法自动横屏
页面旋转后 UI 错乱、布局挤压、约束失效
iPad 分屏后页面布局崩坏,大屏样式适配小窗口
旋转时机错乱,动画卡顿、页面闪烁
iOS16+ 手动旋转代码失效、窗口层级错乱
很多开发者只会简单配置 Info.plist 旋转权限、重写 shouldAutorotate,完全不懂旋转优先级链路、窗口渲染机制、SizeClasses 自适应原理、多窗口生命周期,导致适配代码杂乱、bug 层出不穷。
本文全方位拆解 iOS 屏幕旋转底层逻辑、权限优先级、旋转生命周期、SizeClasses 自适应、iPad 所有多窗口场景、高频踩坑案例、生产级统一适配方案,全程搭配 OC/Swift 实战代码,彻底搞定横竖屏与多窗口适配所有难题。
iOS 屏幕旋转不是物理视图旋转,而是系统重新计算屏幕坐标系、刷新布局、渲染新窗口尺寸的过程。
旋转触发三件事:
系统切换屏幕横竖屏坐标系,修改窗口 bounds/size
触发视图控制器布局刷新、约束重算(AutoLayout 重新求解)
回调旋转生命周期方法,开发者可自定义适配 UI
核心认知:所有横竖屏 UI 错乱,本质是布局未随窗口尺寸动态适配。
iOS 旋转权限遵循严格的优先级链路,权限冲突是旋转失效的核心原因,优先级从上至下递减:
全局窗口锁定,一旦锁定,所有页面均无法旋转,无视控制器配置。
项目全局支持的屏幕方向,默认所有页面继承该配置。
单页面自定义旋转开关,可覆盖全局配置,精准控制单页横竖屏。
用户侧滑、翻转设备,仅在所有权限放行时生效。
所有单页面旋转控制,全部依赖这三个方法,缺一不可:
shouldAutorotate:是否允许自动旋转(开关总闸)
supportedInterfaceOrientations:当前页面支持的旋转方向
preferredInterfaceOrientationForPresentation:页面初始默认方向
场景:整个 App 锁定竖屏,仅播放器页面支持横屏、自动旋转。
第一步:Info.plist 开启所有方向权限(全局放行)
Supported interface orientations 勾选 Portrait、Landscape Left、Landscape Right
第二步:基控制器默认锁定竖屏
objectivec代码解读复制代码// 基控制器默认全局竖屏 - (BOOL)shouldAutorotate { return NO; } -
(UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait; } - (UIInterfaceOrientation)
preferredInterfaceOrientationForPresentation { return UIInterfaceOrientationPortrait; }
swift代码解读复制代码// Swift 基类竖屏配置 override var shouldAutorotate: Bool { false } override
var supportedInterfaceOrientations: UIInterfaceOrientationMask { .portrait } override var
preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { .portrait }
第三步:播放器页面重写,放开横屏权限
objectivec代码解读复制代码// 播放器页面单独支持横竖屏自动旋转 - (BOOL)shouldAutorotate {
return YES; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrie
ntationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight; }
swift代码解读复制代码// 播放器单独支持横屏 override var shouldAutorotate: Bool { true } override
var supportedInterfaceOrientations: UIInterfaceOrientationMask { [.landscapeLeft, .landscapeRight] }
核心避坑:只改页面配置、不改全局配置,会被 Info.plist 权限拦截,旋转失效。
很多 UI 适配错乱、动画卡顿,都是因为选错了适配时机。iOS 旋转有固定生命周期,必须在对应回调中处理布局。
设备翻转 → 系统检测方向变化
询问控制器旋转权限(三大旋转方法)
viewWillTransitionToSize:withTransitionCoordinator: 即将旋转(核心适配时机)
系统执行旋转动画、刷新窗口尺寸
AutoLayout 重新计算约束、刷新布局
旋转完成,页面稳定
viewWillTransitionToSize 是唯一官方推荐的旋转适配回调,旋转前、旋转中可精准修改布局、字体、间距、控件尺寸。
需求:竖屏展示单列布局,横屏展示双列布局、调整按钮尺寸与间距
objectivec代码解读复制代码 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator { [super viewWillTransitionToSize:size
withTransitionCoordinator:coordinator]; // 旋转动画过程中同步更新UI
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
{ if (size.width > size.height) { // 横屏布局适配:双列、放大控件、
调整间距 self.contentView.layoutType = LayoutTypeDoubleColumn;
self.actionBtn.widthConstraint.constant = 160; } else { // 竖屏布局适配:
单列、还原尺寸 self.contentView.layoutType = LayoutTypeSingleColumn;
self.actionBtn.widthConstraint.constant = 120; }
[self.view layoutIfNeeded]; } completion:^(id<UIViewControllerTransitionCoordinator
Context> context) { // 旋转完成后收尾逻辑 }]; }
swift代码解读复制代码 override func viewWillTransition(to size: CGSize, with coordinator:
UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in if size.width > size.height {
// 横屏适配 self.contentView.layoutType = .doubleColumn
self.actionBtn.widthConstraint.constant = 160 } else {
// 竖屏适配 self.contentView.
layoutType = .singleColumn
self.actionBtn.widthConstraint.constant = 120
} self.view.layoutIfNeeded()
}) }
关键知识点:通过 coordinator 绑定系统旋转动画,UI 变更和系统旋转同步,无闪烁、无卡顿、无断层。
单纯判断宽高适配,无法应对 iPad 分屏、浮窗、小尺寸多窗口场景,Size Classes 才是 iOS 响应式适配的终极方案。
iOS 抛弃固定设备尺寸判断,将屏幕抽象为两套维度:
Width Class:屏幕宽度适配等级(Compact / Regular)
Height Class:屏幕高度适配等级(Compact / Regular)
四种组合适配所有场景:手机横竖屏、iPad 横竖屏、分屏、浮窗、多窗口。
设备/场景
宽度等级
高度等级
适配特点
iPhone 竖屏 | Compact | Regular | 窄高屏幕,单列紧凑布局 |
iPhone 横屏 | Regular | Compact | 宽矮屏幕,多列宽松布局 |
iPad 全屏横竖屏 | Regular | Regular | 大屏双栏、复杂布局 |
iPad 分屏窄窗口 | Compact | Regular | 模拟手机尺寸,适配手机布局 |
iPad 浮窗 | Compact | Compact | 小窗口紧凑布局 |
适配 iPad 分屏、窗口缩放、横竖屏切换,通过 traitCollection 动态监听特征变化。
objectivec代码解读复制代码 // 特征变化回调(横竖屏、分屏、窗口缩放都会触发) -
(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; BOOL isCompactWidth = (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact); BOOL isCompactHeight = (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact); if (isCompactWidth && isCompactHeight) { NSLog(@"小窗口/浮窗模式"); [self adaptFloatWindowUI]; } else if (isCompactWidth) { NSLog(@"窄屏模式:手机竖屏/iPad小分屏"); [self adaptCompactUI]; } else { NSLog(@"大屏模式:iPad全屏/横屏"); [self adaptRegularUI]; } }
swift代码解读复制代码 override func traitCollectionDidChange(_ previousTraitCollection:
UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection)
let hCompact = traitCollection.horizontalSizeClass == .compact let vCompact = traitCollection.
verticalSizeClass == .compact if hCompact, vCompact { print("浮窗小窗口模式")
} else if hCompact { print("窄屏适配模式") } else { print
("大屏适配模式") } }
核心优势:无需判断设备、无需判断横竖屏,一套代码适配 iPhone、iPad、分屏、浮窗所有场景。
iOS9 之后 iPad 支持多任务多窗口,绝大多数项目只适配了横竖屏,完全忽略分屏、浮窗场景,导致 iPad 端体验极差、布局崩坏。
屏幕左右分为两个窗口,同时运行两个 App,窗口宽度可自由拖拽缩放,窗口尺寸动态变化,会实时触发 trait 特征变更与布局刷新。
小窗口悬浮在主 App 上方,窗口尺寸固定偏小,属于双 Compact 尺寸特征,需要单独适配紧凑 UI。
iPad 可同时新建多个 App 独立窗口,每个窗口拥有独立尺寸、独立旋转状态、独立生命周期。
误区1:通过 UIDevice 判断 iPad 直接使用大屏布局,分屏后窗口变窄,UI 挤压崩坏
误区2:固定页面宽高比例,窗口缩放后布局错乱
误区3:忽略 trait 变化,仅适配设备旋转,不适配窗口拖拽缩放
误区4:多窗口共享全局变量,导致多窗口状态错乱、数据污染
禁止设备判断适配:永远通过 SizeClasses 特征、窗口尺寸适配,不判断是否为 iPad/iPhone
布局绝对自适应:所有 UI 依赖 AutoLayout 相对约束,禁止固定绝对宽高、固定间距
差异化布局:Compact 模式走手机紧凑布局,Regular 模式走大屏双栏布局
数据窗口隔离:多窗口独立生命周期,禁止全局单例存储页面状态
iPad 多窗口可无限缩小,极小尺寸下 UI 必然错乱,可通过系统 API 限制窗口最小尺寸:
objectivec代码解读复制代码 // AppDelegate 配置窗口最小尺寸适配限制 - (UISceneConfiguration *)
application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)
connectingSceneSession options:(UISceneConnectionOptions *)options { UISceneConfiguration *config
= [UISceneConfiguration configurationWithName:@"Default Configuration" sessionRole:connectingSceneS
ession.role]; // 限制窗口最小宽高,防止过小布局崩坏 config.minimumSize = CGSizeMake(320,
400); return config; }
iOS16 彻底重构了屏幕旋转底层逻辑,废弃旧版手动旋转 API,很多老项目旋转代码失效、播放器无法强制横屏,是高频踩坑重点。
废弃 [UIApplication sharedApplication].statusBarOrientation 直接赋值旋转
旋转权限校验更严格,必须配套控制器旋转配置
支持窗口独立旋转、多窗口不同方向
less代码解读复制代码 // 强制切换为横屏 - (void)forceLandscape {
[self setNeedsUpdateOfSupportedInterfaceOrientations]; if (@available(iOS 16.0, *))
{ [self.navigationController setNeedsUpdateOfSupportedInterfaceOrientations];
self.navigationController.preferredInterfaceOrientationForPresentation =
UIInterfaceOrientationLandscapeRight; } } // 强制切回竖屏 - (void)forcePortrait {
[self setNeedsUpdateOfSupportedInterfaceOrientations]; if (@available(iOS 16.0, *)) {
[self.navigationController setNeedsUpdateOfSupportedInterfaceOrientations];
self.navigationController.preferredInterfaceOrientationForPresentation =
UIInterfaceOrientationPortrait; } }
scss代码解读复制代码 // iOS16+ 强制旋转 func forceLandscape() {
setNeedsUpdateOfSupportedInterfaceOrientations() navigationController?.
setNeedsUpdateOfSupportedInterfaceOrientations() navigationController?.
preferredInterfaceOrientationForPresentation = .landscapeRight } func forcePortrait() {
setNeedsUpdateOfSupportedInterfaceOrientations() navigationController?.
setNeedsUpdateOfSupportedInterfaceOrientations() navigationController?.
preferredInterfaceOrientationForPresentation = .portrait }
关键前提:控制器必须开启 shouldAutorotate = YES,否则强制旋转无效。
问题:普通页面可旋转,导航栈内页面旋转配置不生效。
根源:旋转权限优先读取导航控制器配置,子控制器配置被覆盖。
解决方案:自定义导航栏,转发子控制器旋转配置
objectivec代码解读复制代码 @implementation BaseNavigationController - (BOOL)shouldAutorotate
{ return self.topViewController.shouldAutorotate; } - (UIInterfaceOrientationMask)
supportedInterfaceOrientations { return self.topViewController.supportedInterfaceOrientations; }
@end
问题:旋转后控件位置偏移、重叠、尺寸异常。
根源:控件 frame 硬编码、未使用自适应约束、未在旋转回调刷新布局。
解决方案:全程 AutoLayout 相对约束,在 viewWillTransitionToSize 中刷新布局。
问题:iPad 分屏变窄,依然展示大屏双栏布局,UI 挤压。
根源:判断设备适配而非尺寸特征适配。
解决方案:废弃设备判断,完全基于 SizeClasses 特征差异化布局。
根源:UI 变更未绑定系统旋转动画,时机错乱。
解决方案:所有布局调整统一放在animateAlongsideTransition 动画回调中。
全局配置放开所有旋转权限,通过控制器粒度精准控制旋转
自定义导航栏、标签栏,转发子控制器旋转配置
所有横竖屏 UI 差异化逻辑,统一放在 viewWillTransitionToSize
禁止硬编码 frame,全部使用自适应 AutoLayout
放弃设备型号判断,以 SizeClasses 特征为唯一适配依据
Compact 尺寸复用 iPhone 紧凑布局,Regular 尺寸使用大屏布局
监听 traitCollectionDidChange 实时适配窗口缩放、分屏切换
隔离多窗口数据状态,禁止全局单例存储页面临时状态
Window 锁定 > Info.plist 全局配置 > 控制器页面配置 > 用户手势旋转,优先级从高到低,高优先级覆盖低优先级。
系统优先读取导航控制器的旋转配置,子控制器配置被覆盖。需要自定义导航栏,将旋转权限转发给顶层子控制器。
脱离具体设备尺寸,通过宽窄、高矮等级抽象屏幕状态,一套代码适配 iPhone 横竖屏、iPad 全屏、分屏、浮窗所有场景,是 iOS 响应式布局的核心。
普通旋转仅设备方向变化,分屏是窗口尺寸动态变化+特征变更,不仅需要适配旋转,还需要监听窗口缩放、特征切换,适配更多尺寸场景。
废弃直接修改状态栏方向的旋转方式,改为通过控制器、导航栏的 setNeedsUpdateOfSupportedInterfaceOrientations 刷新旋转状态,强制旋转必须依赖控制器权限配置。
作者:MonkeyKing
链接:https://juejin.cn/post/7647066548545749026
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。