一个仍使用旧版 Launch Image 的 App,在 iOS 26 上没有收到异常反馈,到了 iOS 27,点击输入框唤起键盘,再点击页面空白处收起,整个页面向下偏移,顶部留下一大片空白。
真机采样发现:同一个 App 进程里,UIScreen.mainScreen.bounds 返回 375 × 812,键盘通知返回的宽度却是 430,收起后的顶部 Y 坐标是 932。
这里的 812 正是 Screen API 返回的高度,用它推算键盘位置,仍然会算错。
同一个 App 里,Screen 与键盘通知返回了什么?
这台设备竖屏的逻辑屏幕尺寸是 430 × 932。应用内采样如下,所有数值的单位都是点,也就是 pt:
| 数据来源 | 运行时返回值 |
|---|---|
UIScreen.mainScreen.bounds |
{{0, 0}, {375, 812}} |
| Window 和页面 bounds | {{0, 0}, {375, 812}} |
键盘弹起后的 UIKeyboardFrameEndUserInfoKey |
{{0, 539}, {430, 393}} |
键盘收起后的 UIKeyboardFrameEndUserInfoKey |
{{0, 932}, {430, 393}} |

对这个竖屏、底部停靠键盘的样本,键盘宽度 430 与 Screen 宽度 375 不同;弹起时的键盘底边是 539 + 393 = 932,也对不上 Screen 高度 812。收起后的 y=932 表示键盘顶部已退到屏幕底部,键盘自身的大小仍是 430 × 393。
两边的数值都是 pt,除以 @3x 也无法消除这组坐标差异。
Apple 对 keyboardFrameEndUserInfoKey 的说明指出,键盘 frame 属于屏幕坐标系,需要参与视图布局时应转换到目标视图坐标系,再计算交集。Apple 文档:keyboardFrameEndUserInfoKey
本次没有同一台设备升级前后的对照采样,不能倒推出"老系统返回 812,iOS 27 改成了 932",也不能将这个样本推广到所有 App。
Screen 为什么仍然返回旧尺寸?
工程中存在 LaunchScreen.storyboard 文件,但产物没有配置 UILaunchStoryboardName,也没有采用 UILaunchScreen,实际使用的是旧版 Launch Image。对应全面屏的竖屏启动图条目仍是 375 × 812,与应用读到的 Screen、Window 和页面尺寸相同。
这与旧 App 的兼容放大布局表现一致:应用以旧的逻辑尺寸排版,再放大显示到设备屏幕上。这里的"兼容放大"指应用适配行为,与用户设置中的"显示缩放"不同。
Apple 在介绍启动适配时提到,旧版 Launch Images 需要为每一种支持的屏幕尺寸提供图片,并随着新尺寸增加而更新;启动屏 Storyboard 则是推荐的适配方式。WWDC:Modernizing Your UI for iOS 13
启动配置是背景线索,但本次没有做仅切换启动屏的对照实验,尚不能认定更换启动屏就能解决问题。旧代码的错误则可以直接复算。
一段同时算错弹起和收起的代码
旧逻辑监听 UIKeyboardWillChangeFrameNotification,简化后如下。liftDistance 是页面自己维护的上移距离,不是系统提供的键盘高度:
objective-c
CGRect keyboardFrame =
[notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardY = CGRectGetMinY(keyboardFrame);
CGFloat screenHeight = CGRectGetHeight(UIScreen.mainScreen.bounds);
if (keyboardY == screenHeight - CGRectGetHeight(keyboardFrame)) {
self.view.transform =
CGAffineTransformMakeTranslation(0, -liftDistance);
} else {
self.view.transform = CGAffineTransformMakeTranslation(
0, keyboardY - CGRectGetHeight(self.view.frame));
}
它用键盘坐标与 Screen 高度的等式判断键盘是否弹起,其他情况全部进入回落分支。
代入采样数据:
text
弹起:539 == 812 - 393,判断为 false
误入 else:539 - 812 = -273
收起:932 - 812 = 120
写入向下 120 pt 的平移,没有恢复原位
弹起和收起两个分支都受到了影响。放宽浮点数相等判断也解决不了问题,因为参与计算的坐标本身就不能直接混用。
修复:按事件恢复状态,只移动内容容器
这个表单页原本采用固定上移策略。修复将监听改为 UIKeyboardWillShowNotification 和 UIKeyboardWillHideNotification,直接按显示、隐藏事件切换位置;只移动表单容器,避免修改控制器根视图。
核心代码如下。formView 是表单容器,keyboardLift 沿用页面自己维护的上移距离,与前面的 liftDistance 含义相同:
objective-c
// 由键盘将显示、将隐藏通知调用。
- (void)handleKeyboardVisibility:(NSNotification *)notification {
BOOL showing =
[notification.name isEqualToString:UIKeyboardWillShowNotification];
self.formView.transform = showing
? CGAffineTransformMakeTranslation(0, -self.keyboardLift)
: CGAffineTransformIdentity;
}
示例省略了通知注册、当前页面焦点检查、键盘同步动画、页面退出复位和观察者清理;实际修复包含这些处理。隐藏时直接恢复单位变换,不再根据键盘坐标计算回落距离。
固定上移距离只适用于这类已有固定布局的页面。需要动态避让时,应先转换键盘坐标并计算交集,或在支持的系统版本上采用 UIKeyboardLayoutGuide。Apple 布局指南
验证时保留原来的触发条件
验证包保留原来的启动配置、Xcode 和 SDK 组合。修复后,UIScreen.mainScreen.bounds 仍为 375 × 812,键盘收起通知仍返回 y=932,但反复收放键盘和切换前后台后,页面已不再偏移。