iOS 27 兼容放大模式下,keyboardFrame 与 UIScreen.bounds 的坐标差异

一个仍使用旧版 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,但反复收放键盘和切换前后台后,页面已不再偏移。

相关推荐
HEJOO93 小时前
iOS 高效绘图:从 Core Graphics 到 Metal 的实践指南
macos·ios·cocoa
HEJOO94 小时前
iOS 性能优化全面详解
ios·性能优化·cocoa
ii_best4 小时前
iOS 自动化脚本工具更新|按键精灵 适配 Relaxin(青霉素)越狱,兼容 iOS16~iOS26 主流越狱环境
ios·自动化·按键精灵
万年咸鱼4 小时前
iOS 27 适配笔记
笔记·ios·cocoa
HEJOO95 小时前
iOS Dark Mode 适配笔记
笔记·ios·cocoa
风雨_7 小时前
Mac M3 使用 OrbStack 全新安装运行 EasyConnect 完整教程
ios
sakiko_9 小时前
纯Appdelegate控制的老项目适配Xcode27(iOS27SDK)
macos·objective-c·cocoa
Coder_张三1 天前
iOS 26 适配
前端·ios·swift
用户38034165882971 天前
崩溃率从 0.3% 到 0.04%:我们做对的不是修 bug,是让每个崩溃只发生一次
ios·swift