PKToolPicker工具栏图标错误模糊问题排查报告

iOS 26 踩坑记:PKToolPicker 工具栏图标"雾化"之谜 ------ 一场由 Liquid Glass 引发的渲染 Bug

iOS 26 上,当 PKToolPicker 的工具数量 < 6 (工具区不溢出、全部展开不可拖动)时,Liquid Glass 材质会在初始化阶段把工具图标渲染成持续的"雾化/模糊"中间态;工具数 ≥ 6 时则完全正常。
遇到问题后我们在互联网上查阅了多个渠道,没有查到任何关于该问题的已知报告或讨论。也就是说,这很可能是一个尚未被广泛报道的 iOS 26 系统渲染 Bug。


一、问题现象

在 iOS 项目中使用 PKToolPicker + 自定义 toolItems 时,工具栏图标出现诡异的雾化 / 模糊 / 残影效果:

  • 笔、橡皮擦、颜色选择器等图标边缘发虚,带有拖影;
  • 整体像被一层高斯模糊覆盖,或像卡在动画的中间态;
  • 工具栏常驻展示时必现
  • 神奇的是:收起(setVisible(false))再展开,或把工具栏拖动一下,图标就恢复清晰了(左右雾化仍在)。

关键特征归纳

经过反复测试,现象有三个决定性规律:

场景 表现
工具数 < 6,首次初始化 工具区上下左右四个方向都有模糊
工具数 < 6,收起后重新展开(或在平板状态重新展开 / 先切到 ≥6 再切回 <6) 左右两侧有模糊
工具数 ≥ 6 无论如何都不会出现该问题

这里的关键机制是:工具数 < 6 时工具区不溢出------所有工具全部平铺展开、不能左右拖动;而 ≥ 6 时工具区进入可滚动/溢出状态,走的是另一条渲染路径,恰好绕开了这个 Bug。


二、排查历程:排除了哪些"嫌疑人"

这个 Bug 极具迷惑性,我们前后排除了一长串错误方向,这部分经验可能比结论本身更有价值。

❌1:显示时机 / 第一响应者问题

假设setVisible(true, forFirstResponder:) 调用过早(view 还没进 window、还没成为第一响应者),导致初始动画没走完。

验证与修复尝试 :将激活时机收敛到 viewDidAppear 之后、确认 view 已进入 window、成功成为第一响应者后再显示,并加上幂等保护:

swift 复制代码
func activate(for responder: UIResponder) {
    guard !isInvalidated,
          let responderView = responder as? UIView,
          responderView.window != nil else { return }
    // 幂等保护:同一 responder 不重复 setVisible
    guard !(self.responder === responder && responder.isFirstResponder) else { return }
    guard responder.isFirstResponder || responder.becomeFirstResponder() else { return }
    // ...
    toolPicker.setVisible(true, forFirstResponder: responder)
}

结果 :时序收敛、幂等保护全部加上,问题依旧。排除。

❌2:Core Animation 残留(presentationLayer 卡在中间态)

假设 :图标的 CALayer 卡在动画中间态,或残留了 gaussianBlur filter、shouldRasterize 错误缩放等。

验证 :加入临时诊断代码,枚举所有 scene window、dump 每个 layer 的 transform / opacity / contentsScale / shouldRasterize/ rasterizationScale / filters / animationKeys() 及 model layer 与 presentation layer 的差异。

结果:没有发现业务代码导致的异常属性;且"动一下就恢复"的模式也不支持"属性残留"解释。排除。

❌3:自定义 icon 资源 / 图片 scale 问题

假设PKToolPickerCustomItemimageProvider 返回的图片 scale 不对(1x 图渲染到 3x 屏)。

结果 :换成纯系统 SF Symbol 依然模糊;且连 PencilKit 内置的笔/橡皮图标也一起模糊。排除。

❌4:responder 类型不对

假设 :Picker 绑定的 responder 必须是 PKCanvasView,用普通 UIView 会出问题。

结果 :换成 PKCanvasView 作为 anchor/observer,依然模糊。排除。


三、关键突破:基于 Apple 官方示例的对照实验

真正的突破口来自一个反例 :Apple 官方示例 Configuring the PencilKit tool picker 也自定义了工具栏,却完全不模糊。

仔细对比代码后发现关键差异------官方示例虽然自定义了工具,但追加了完整的系统默认工具列表 。于是我们直接修改这份官方示例做对照实验:去掉完整默认列表,只保留少量受限 items:

结果 :仅仅是改变了 toolItems 数组内容,同一份官方示例立刻复现了完全一致的雾化现象------所有 item 边缘发虚、图标周围明显 blur、工具栏内部像被低分辨率缩放。

这个实验干净利落地证明了:

  1. 不是自定义 icon 资源问题------示例用的还是原来的自定义 Stamp 图标;
  2. 不是 PKToolPickerCustomItem 图片 scale 问题------图片相关代码一行没改;
  3. 不是业务代码/生命周期问题------官方示例的时序代码原封不动;
  4. 触发条件就是 toolItems 列表本身PKToolPicker().toolItems 返回的完整默认列表携带了 Apple 内部的 metadata(preferred size、rendering mode、toolbar grouping 等),当列表完整(≥6,工具区溢出可滚动)时渲染正常;换成极简的受限列表(<6,全部展开不可拖动)时,PencilKit 内部的 snapshot/rasterize 缓存路径就出现了错误的 backing scale。

对照汇总:

实验组(均为官方示例代码) toolItems 构成 结果
原始示例 自定义项 + 完整默认列表(≥6,溢出) ✅ 清晰
修改后示例 [customStamp, pen, eraser](<6,不溢出) ❌ 雾化

四、根因分析:Liquid Glass 材质的初始化渲染缺陷

iOS 26 引入了全新的 Liquid Glass(液态玻璃) 材质体系,PKToolPicker 的悬浮工具栏正是 Glass 材质的典型应用场景------半透明、模糊背景、悬浮圆角面板。

结合实验数据,推断内部机制如下:

bash 复制代码
工具数 < 6(不溢出,全部平铺展开)
    ↓
PKToolPicker 使用「全展开固定布局」
    ↓
Liquid Glass 材质初始化时,snapshot/rasterize 走了错误路径
    ↓
生成低分辨率 / 错误 backing scale 的位图缓存
    ↓
Retina 屏上 1x 位图 → 3x 显示 → 边缘插值模糊
    ↓
且该中间态不会被后续布局刷新纠正(持续雾化)

几个现象都能被这个模型解释:

  1. 首次初始化"上下左右都有模糊" :Glass 面板整体连同边缘高亮一起进入了错误的光栅化状态;
  2. 重新展开后"仅左右模糊" :重新展开触发了面板主体的重新渲染(恢复正常),但左右两侧的 Glass 边缘/阴影区域仍沿用错误缓存;
  3. ≥ 6 时不触发:溢出布局使用可滚动容器,Glass 材质走了另一套(正确的)渲染管线;
  4. "移动一次工具栏就变清晰" :拖动会强制 Glass 视图重新计算 frame 并重建材质快照------这就是为什么用户会发现"动一下就好了"。

五、可行方案对比

方案一:追加完整默认 items(让工具数 ≥ 6)

scss 复制代码
let toolItems = [customItem] + PKToolPicker().toolItems

确实能修复模糊(官方示例就是证据),但会把 Lasso 选择、标尺等产品不支持/不想要的系统工具全部暴露出来。不能采用。

方案二:iOS 26+ 自动模式下回退自绘工具栏

如果项目中本身有自绘工具栏实现,可在工具栏决策层增加版本门槛:

swift 复制代码
static func needsRestrictedPencilPickerWorkaround(
    operatingSystemVersion: OperatingSystemVersion = ProcessInfo.processInfo.operatingSystemVersion
) -> Bool {
    operatingSystemVersion.majorVersion >= 26
}

// 自动模式解析:iOS 26+ 回退自绘工具栏
if Self.needsRestrictedPencilPickerWorkaround() {
    return .legacy
}

优点:

  • 零私有 API、零 KVC、无审核风险;
  • 用户体验一致(自绘工具栏功能完全可控);
  • 显式指定 PencilKit 的调用方不受影响,便于未来 Apple 修复后灰度切回。

方案三:利用"移动/重展开即恢复"机制做公开 API 刷新

既然"拖动一次"能恢复清晰,理论上可以在首次显示后的下一帧执行一次重置,但是这个方案也只是停留在理论,尚未找到有效的重置手段,有待进一步探索。


六、经验总结

  1. 善用反例定位变量:Apple 官方示例"自定义了工具栏却不模糊"是本案最大的突破口。遇到灵异 Bug 时,找一个"看起来一样但没病"的对照组,只改一个变量做减法,远比盯着症状猜原因高效。
  2. 基于官方示例做修改是最干净的复现方式 :直接在 Apple 官方 Demo 上改 toolItems,排除了自身工程的一切干扰因素,证据链无懈可击------这也让结论更适合作为 Feedback 提交给 Apple。
  3. "时序修复"要敢于证伪 :我们第一轮的 viewDidAppear 延迟激活 + 幂等保护是非常合理的修复,但验证无效后没有执迷,而是把它降级为"实验变量"继续排查------最终证明根因完全在别处。
  4. iOS 大版本首发的系统组件要谨慎 :Liquid Glass 是 iOS 26 的全新渲染体系,PKToolPicker 这类高度依赖 Glass 材质的系统组件,在非标准用法(受限 toolItems)下踩中渲染缺陷并不意外。如果你的产品也在 iOS 26 上遇到系统控件"莫名模糊",先检查是不是触发了某种边界布局条件
  5. 保留可回退的架构:我们最终能低成本规避,得益于项目中本来就存在自绘工具栏 / PencilKit 双实现 + 自动路由。对系统组件的依赖,永远给自己留一条退路。

七、环境信息 & 参考链接

复现环境

  • iOS 26.5 Simulator / iPhone 17 Pro
  • Xcode 26.x,PencilKit,PKToolPicker(toolItems:) API
  • 复现工程:Apple 官方示例 ConfiguringThePencilKitToolPicker(仅修改 toolItems 数组)

检索说明

  • 截至本文发布,在 Apple Developer Forums、Stack Overflow 等公开渠道均未查到该问题的相关报告,官方文档与 Release Notes 中亦无提及。本文应是该问题的首次公开记录,欢迎有类似经历的同学在评论区补充数据点(真机表现、其他 iOS 26 小版本等)。

参考资料

相关推荐
2501_915921432 小时前
详细解析,iOS 应用上架 App Store 的完整流程与指南
android·ios·小程序·https·uni-app·iphone·webview
2501_915106322 小时前
SwiftUI项目创建详解:使用Xcode从零开始创建第一个App项目
ide·vscode·ios·swiftui·个人开发·xcode·敏捷流程
9765033354 小时前
iOS 上架/审核 4.3a Cocos 2026最新方案解读
flutter·ios·swift·cocos2d·ios开发
2501_916007474 小时前
IDE 是什么?集成开发环境详解与 iOS 开发选型指南
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
CocoaKier13 小时前
喜大普奔!2027年春季起,iOS提审最低支持iOS15.0以上
ios·apple
初级代码游戏16 小时前
iOS开发 Swift 速记2:三种集合类型 Array Set Dictionary
开发语言·ios·swift
凡泰AI20 小时前
APP同时覆盖了iOS、安卓和鸿蒙,如何选择混合开发架构才能减少重复建设,提高功能上线效率~
android·ios·harmonyos·mpaas·uni·小程序容器
AIsoft_86881 天前
iPhone录音重点标记:会议录音与重点内容同步保存功能实测
ios·iphone
00后程序员张1 天前
使用Instruments工具深入分析iOS应用性能与启动时间优化
android·macos·ios·小程序·uni-app·cocoa·iphone