Telegram 技巧档案UIKIT · APPKIT

技巧索引

按框架、源码项目、验证方式、章节和标签筛选全部 300 条技巧。

下面列出 15 份主题笔记中的全部条目。筛选条件会写入地址栏,所以可以直接分享筛选结果。例如,同时选中“AppKit”和“私有 API”标签,就能列出 macOS 端依赖未公开接口的条目。点击条目标题进入章节页,查看代码摘录和证据链。

显示 300 / 300 条

  • #01-01UIKit运行时与私有 API精选 01-1
    借安全文本输入层给任意图层打上截图排除标记

    代码找到 UITextField 的内部 TextLayoutCanvasView。代码临时用 KVC 把目标 CALayer 替换成该视图的 layer。代码切换 secureTextEntry 后恢复原图层。秘密聊天的历史节点会调用此函数。

    UIKitUtils.m · L299–L328
  • #01-02UIKit运行时与私有 API精选 01-2
    用 XOR 字节串隐藏私有拼写控制器,并用精确 ABI 调用它

    代码将私有类和 selector 的 UTF-8 字节逐字节 XOR 0x5A。代码只在运行时解码并查找。代码把带 BOOL 的语法检查调用转换为准确的 C 函数类型。 混淆还原:我按 byte ^ 0x5A 解码源码数组。结果是 UITextCheckingController、``…

    NativeTextChecking.swift · L4–L88
  • #01-03UIKit运行时与私有 API精选 01-3
    伪造 Info.plist 结果,让 App-Bound 域名可以动态增删

    代码交换 NSBundle.objectForInfoDictionaryKey:。当 WebKit 查询 WKAppBoundDomains 时,代码返回带引用计数的运行时域名列表。代码再经 dlsym 调用私有重载函数刷新指定数据存储。 混淆还原:@"WK" + @"Website…

    UIViewController+Navigation.m · L518–L543
  • #01-04UIKit运行时与私有 API
    从私有键盘场景代理取得键盘窗口,并避开新版陷阱

    代码为未声明接口声明最小协议。代码通过 UIWindowScene.keyboardSceneDelegate.keyboardWindow 取得内部键盘窗口。每一步都先检查 selector 和结果类型。

    UIViewController+Navigation.m · L333–L350
  • #01-05UIKit运行时与私有 API精选 01-4
    用私有 cornerRadii 获得可动画的四角半径,避免离屏遮罩

    代码探测私有 setter setCornerRadii:。代码按 QuartzCore 类型编码把四个 CGSize 装入 NSValue。代码用 KVC 读写私有 cornerRadii 属性。

    CALayerCornerRadii.swift · L37–L124
  • #01-06UIKit运行时与私有 API
    交换继承方法前先落地到目标类,避免污染父类

    代码先尝试向目标类添加原 selector。添加成功说明原实现继承自父类。代码此时只在目标类安装交换后的入口,并把父类 IMP 放到替代 selector。只有原方法本来就在目标类时才直接交换。

    RuntimeUtils.m · L21–L32
  • #01-07UIKit运行时与私有 API
    把 UIMenuItem 的文字渲染偷换成图标渲染

    代码给带图标的菜单标题追加 U+FEFF U+200B 隐形后缀。代码交换 NSString.sizeWithAttributes: 与 UILabel 的绘制、布局和 frame setter。代码根据标题反查菜单项,抑制文字并嵌入居中的 UIImageView。

    UIMenuItem+Icons.m · L30–L164
  • #01-08UIKit运行时与私有 API
    从私有 _scrollHeartbeat 抓显示链接,强制旧系统滚动使用满帧率

    代码在 iOS 15 上以 KVC 读取 UIScrollView 的私有 _scrollHeartbeat。代码标记该 CADisplayLink,并请求屏幕最大帧率。另一个 CADisplayLink swizzle 会再次把带标记的范围固定到最大值。 混淆还原:@"_" +…

    UIViewController+Navigation.m · L291–L317
  • #01-09UIKit运行时与私有 API精选 01-5
    先读私有方法签名,再选择 float 或 double 的 IMP 调用约定

    代码动态查找 CASpringAnimation 的私有求值 selector。代码读取第一个显式参数的 Objective-C 类型编码。代码按 float 或 double 选择不同函数指针。找不到时退化为线性值。 混淆还原:@"_" + @"solveForInput:" 还原…

    UIKitUtils.m · L22–L49
  • #01-10UIKit运行时与私有 API
    给弹簧动画写入私有高帧率原因码

    代码组合公开的 preferredFrameRateRange 和私有 KVC 键 highFrameRateReason。代码只在 iOS 15 及以上设置这两项。

    UIKitUtils.m · L68–L83
  • #01-11UIKit运行时与私有 API
    用 NSInvocation 安全探测并配置私有模糊效果

    代码在 iOS 11 到 iOS 12 解析私有类,并经 NSInvocation 调用存在的 setter。辅助函数先取得 NSMethodSignature。不存在的 setter 会成为无操作。 混淆还原:类名是 _UICustomBlurEffect。setter 依次是 se…

    UIKitUtils.m · L110–L200
  • #01-12UIKit运行时与私有 API
    将私有 _UIPortalView 包装为最小协议,并关闭其命中测试转发

    代码拼接并解析私有门户视图类。代码以本地 UIKitPortalViewProtocol 访问已知属性。代码把门户设为不接收触摸,并按需要同步源视图的位置和变换。 混淆还原:数组拼接结果是 _UIPortalView。代码还通过同样的解析确认一个视图是否为该类,并读取其 sourceVie…

    UIKitUtils.m · L211–L257
  • #01-13UIKit运行时与私有 API精选 01-6
    检验私有方法的完整类型编码后才替换 IMP

    代码仅在 iOS 26 及以上注册。代码解析 UIKit Swift mangled 类名,再找 backdropLayer:didChangeLuma:。代码要求完整编码正好是 v32@0:8@16d24 后,才保存原 IMP 并替换。 混淆还原:TGEncodeText 的键为 0…

    UIViewController+Navigation.m · L378–L420
  • #01-14UIKit运行时与私有 API
    通过私有 selector 让悬浮窗口既不改状态栏,也不夺取键盘焦点

    AboveStatusBarWindow 处于状态栏之上。代码在类初始化时,给它动态添加两个私有 selector。两个 selector 都复用返回 false 的公开命名实现。 混淆还原:第一个 selector 是 _canAffectStatusBarAppearance。第二个…

    UIViewController+Navigation.m · L890–L941
  • #01-15UIKit运行时与私有 API
    Debug 录制器直接替换 UIKit 基类 IMP,采集原始滚动物理数据

    调试专用录制器把 UIPanGestureRecognizer 的四个触摸入口替换为 block IMP。代码先执行原实现,再记录已经更新的速度和状态。代码还 hook setContentOffset:,以及存在时的私有橡皮筋计算器。

    UIScrollViewPhysicsSwizzler.swift · L75–L133
  • #01-16UIKit运行时与私有 API
    关闭 UITextView 分块渲染,并识别 iOS 16 私有自动更正提示

    构造输入框时,调用存在才执行的私有 _disableTiledViews。iOS 17 前,代码加一个不延迟也不取消触摸的 tap recognizer。它会把 @"UI" + @"AutocorrectInlinePrompt" 解析为 UIAutocorrectInlinePrompt…

    ChatInputTextViewImpl.m · L23–L70
  • #01-17UIKit运行时与私有 API
    旧系统用 KVC 强制设备方向,新系统改走场景几何更新

    代码将方向策略切为仅竖屏时,在 iOS 16 以上请求公开的 UIWindowScene.GeometryPreferences。旧系统则向 UIDevice 的 KVC 键 orientation 写入竖屏 raw value。

    NativeWindowHostView.swift · L103–L123
  • #01-18UIKit运行时与私有 API
    重写私有 UIWindow 方向回调,精确包住布局中的旋转状态

    NativeWindow 覆盖私有 _update(toInterfaceOrientation:duration:force:)。代码在 super 前后切换布局中标志。代码在结束后把转换后的方向回传给自定义窗口宿主。

    NativeWindowHostView.swift · L318–L339
  • #01-19UIKit运行时与私有 API
    对运行时类表做哈希索引,避免私有类名进入二进制

    Freedom 枚举全部 Objective-C 类。代码用带固定 seed 的 MurmurHash3 把类名映射到 uint32_t。调用点只保留哈希常量,再从映射获得运行时类。

    Freedom.mm · L124–L155
  • #01-20UIKit运行时与私有 API
    自动生成装饰子类,再从 alloc 时替换每个实例的 ISA

    代码动态创建 Decorated<hash> 子类。代码从另一个类复制指定方法到装饰类。代码把原类元类的 alloc 替换为 freedomAllocImpl,所以每个新实例都会在分配后被 object_setClass 成装饰类。

    Freedom.mm · L469–L524
  • #01-21UIKit运行时与私有 API
    直接改写系统返回手势识别器的 ISA,增加全屏导航手势

    代码把 UIKit 创建的 interactivePopGestureRecognizer 改成自定义 TGNavigationPanGestureRecognizer。代码保留系统原 delegate,并以代理注入自定义开始和同时识别判断。遇到 QLPreviewController…

    TGNavigationController.m · L129–L182
  • #01-22UIKit运行时与私有 API
    把 NSNotificationCenter 的同步投递改造成可截获的中间件

    代码全局交换通知中心的同步投递方法。主线程处理器可先做工作、调用 passNotification 决定投递时机,或返回 true 完全吞掉投递。方向处理模块用它在旋转通知前后维护状态。

    NotificationCenterUtils.m · L19–L68
  • #01-23UIKit运行时与私有 API
    把截图重复通知去重状态放在每个 observer,避免覆盖层漏报

    代码以每个 ScreenshotNotificationObserver 自己的最后时间戳去重。相隔少于 0.5 秒的相同截图通知会被丢弃。

    ScreenCaptureDetection.swift · L53–L88
  • #01-24UIKit运行时与私有 API
    只把 CAFilter 当作运行时工厂,不引入私有头文件

    代码以本地 GraphicsFilterProtocol 描述唯一需要的类方法。代码用 NSClassFromString(@"CAFilter") 解析私有 Core Animation 类。各工厂直接按滤镜名调用 filterWithName:。

    UIKitUtils.m · L259–L295
  • #02-01UIKit窗口、键盘与状态栏精选 02-1
    经私有键盘场景委托取得键盘窗口

    它从 UIWindowScene 的 keyboardSceneDelegate 取 keyboardWindow。AppDelegate 先走此路径,再回退扫描所有场景窗口。

    UIViewController+Navigation.m · L761–L782
  • #02-02UIKit窗口、键盘与状态栏
    用类名形状兼容多代系统键盘视图

    它按前后缀匹配 UIRemoteKeyboardWindow、UITextEffectsWindow、UIInputSetHostView 和 UIKeyboardItemContainerView。它也匹配容器 UIInputSetContainerView 与 UITrac…

    AppDelegate.swift · L73–L106
  • #02-03UIKit窗口、键盘与状态栏精选 02-2
    直接移动系统键盘层,而非只移动应用内容

    它把键盘视图的 bounds.origin.y 设为负偏移。它再对同一 CALayer 加加性位移动画。窗口布局同时把输入高度扣除该偏移。

    KeyboardManager.swift · L65–L79
  • #02-04UIKit窗口、键盘与状态栏精选 02-3
    用多个坐标修正键盘通知的错误高度

    它优先信任实时键盘视图的 frame.minY。iOS 16.1 以上,它把通知中的屏幕坐标转换到事件窗口。它还修正居中窗口的半个高度差、iPad 的近边界误差和子窗口的 27pt 偏差。

    WindowContent.swift · L650–L761
  • #02-05UIKit窗口、键盘与状态栏
    全窗口手势用第一响应者和附件高度划定拖拽区

    它把窗口级拖拽限制在键盘上方。它沿命中视图向上读取禁用标记。它把输入附件高度计入键盘上界,并以 3pt 阈值开始更新布局。

    WindowContent.swift · L1489–L1513
  • #02-06UIKit窗口、键盘与状态栏精选 02-4
    键盘收起手势显式取消同一手指的列表惯性

    它在触摸结束期间记录“此手势已收起键盘”。列表稍后收到自己的 .ended 时可读取该值并放弃惯性滚动。

    WindowContent.swift · L1547–L1573
  • #02-07UIKit窗口、键盘与状态栏
    伪造选区变化以触发文本自动更正

    它先取消 marked text。它把选区左移一个字符。它立即恢复原选区。

    UIViewController+Navigation.m · L850–L859
  • #02-08UIKit窗口、键盘与状态栏精选 02-5
    将系统键盘窗口横向平移到第一响应者所在表面

    它根据第一响应者宿主的屏幕 X 坐标改 keyboardWindow.layer.sublayerTransform。它可限制只向前或向后平移。响应者消失时,它短暂隐藏窗口并递归取消 position 与 bounds 动画。

    KeyboardManager.swift · L99–L123
  • #02-09UIKit窗口、键盘与状态栏
    抓取系统状态栏后按像素重着色

    它把每个系统状态栏子视图渲染进 DrawingContext。它再按 alpha 把像素改为黑色或白色。最后用 ASDisplayNode.contents 在相同坐标复刻图像。

    StatusBarProxyNode.swift · L153–L194
  • #02-10UIKit窗口、键盘与状态栏
    运行时拼接私有状态栏类名

    它不在单个字面量中放完整私有类名。CFAbsoluteTimeGetCurrent() > 0 在正常运行时为真,因此代码会拼出完整名称后再传给 NSClassFromString。

    StatusBarProxyNode.swift · L360–L414
  • #02-11UIKit窗口、键盘与状态栏
    用公共 RunLoop 定期重抓状态栏的活动元素

    代理显示时立即重抓条目。它随后每五秒在 .common RunLoop 模式重抓。隐藏时它销毁定时器。

    StatusBarProxyNode.swift · L482–L499
  • #02-12UIKit窗口、键盘与状态栏
    把多个节点树的状态栏决策汇聚到唯一根控制器

    Window1 依次让根导航、普通展示层和顶层展示层覆盖状态栏值。WindowRootViewController 保存最终值,并调用 setNeedsStatusBarAppearanceUpdate()。

    WindowContent.swift · L1188–L1208
  • #02-13UIKit窗口、键盘与状态栏精选 02-6
    全局覆盖层在键盘可见时改挂到键盘窗口

    键盘可见且有真实高度时,它把全局控制器的父视图改为 keyboardWindow。键盘不存在时,它改回普通窗口的 parentView。

    GlobalOverlayPresentationContext.swift · L72–L105
  • #02-14UIKit窗口、键盘与状态栏
    展示上下文不用 UIKit containment,而是手动生命周期

    它等待 ContainableController.ready,最多两秒。它直接配置 view 和节点布局。随后代码直接插入子视图,并手动调用 viewWillAppear 和 viewDidAppear。

    PresentationContext.swift · L157–L244
  • #02-15UIKit窗口、键盘与状态栏
    自定义 UIWindow.hitTest 特判系统编辑菜单

    自定义窗口先倒序测试 UIKit 加在窗口顶层的编辑菜单。只有未命中时,它才分发给 Telegram 的覆盖层和根内容。

    WindowContent.swift · L957–L999
  • #02-16UIKit窗口、键盘与状态栏
    透明伪滚动视图接管状态栏“回到顶部”手势

    它在窗口上覆盖一个透明 UIScrollView。它伪造 1000pt 可滚动距离。状态栏点击触发代理回调,但 delegate 返回 false,因此代理本身不滚动。

    ScrollToTopProxyView.swift · L4–L37
  • #02-17UIKit窗口、键盘与状态栏
    仅全屏窗口套用按设备表测得的安全区

    它把窗口与屏幕的纵向尺寸逐项比较。差超过 1pt 时,它完全放弃设备刘海表。多显示屏设备则直接读窗口的系统安全区。

    WindowContent.swift · L104–L140
  • #02-18UIKit窗口、键盘与状态栏
    用型号、缩放率和屏幕尺寸选择经验设备档案

    它从 utsname.machine 读取型号。它先按型号筛档案,再用 scale 与屏幕尺寸确认。它把高度加 20pt 后再比一次,以兼容状态栏尺寸差异。

    DeviceMetrics.swift · L57–L76
  • #02-19UIKit窗口、键盘与状态栏
    把灵动岛与刘海绘制成一张按机型量出的几何表

    它为每代设备指定缺口宽、高和顶部偏移。显示缩放模式不另存一份表,而是把标准机型几何按宽度比例缩放。

    DeviceMetrics.swift · L487–L533
  • #02-20UIKit窗口、键盘与状态栏
    双显示屏型号绕开整张设备指标表

    对型号 iPhone19,4,它不信任启动时匹配出的设备档案。它改读当前窗口的系统安全区和状态栏。状态栏暂时消失时,它按屏幕尺寸缓存上一次可见高度。

    DeviceMetrics.swift · L71–L76
  • #02-21UIKit窗口、键盘与状态栏
    方向锁定同时兼容现代 Scene API 与旧 KVC

    iOS 16 以上用 requestGeometryUpdate 锁竖屏。旧系统用 KVC 写 UIDevice.orientation。其他方向变更则请求 UIKit 重试设备方向。

    NativeWindowHostView.swift · L103–L123
  • #02-22UIKit窗口、键盘与状态栏
    在 UIKit 继承动画期间手工补一条窗口层帧动画

    自定义 UIWindow setter 读取 UIView.inheritedAnimationDuration。有继承动画时,它在窗口层追加同持续时间的 animateFrame。无尺寸变化时,它避免布局通知。

    NativeWindowHostView.swift · L263–L287
  • #03-01UIKit布局与动画
    用私有 cornerRadii 实现零掩膜的四角圆角

    它探测私有 setter setCornerRadii:。它通过 KVC 读写私有 cornerRadii。CornerRadii.boxedValue 按 CACornerRadii 的四个 CGSize 布局封箱。调用方还能从展示层读取中断时的实际圆角。

    CALayerCornerRadii.swift · L95–L119
  • #03-02UIKit布局与动画精选 03-1
    劫持 UIKit 事务内的弹簧动画

    ComponentTransition.animateView 先把覆盖参数压栈。它调用 UIView.animate。+load 再交换 -[CALayer addAnimation:forKey:]。交换后的实现会复制 UIKit 生成的动画,并替换为指定刚度、阻尼或三次贝塞尔曲…

    UIViewController+Navigation.m · L191–L215
  • #03-03UIKit布局与动画
    调用私有 _solveForInput: 统一手工插值和系统弹簧

    ListViewAnimation 不自行近似系统弹簧。运行时工具将字符串 "_" + "solveForInput:" 还原为 _solveForInput:。它读取方法签名,并按 float 或 double 调用 IMP。找不到时它回退为线性 t。

    ListViewAnimation.swift · L91–L110
  • #03-04UIKit布局与动画精选 03-2
    借用未公开的 UIKit 曲线值 7 << 16

    代码把 UIView.AnimationOptions 的曲线字段设为 7。它把该值命名为 .spring。ContainedViewLayoutTransition 对 .spring 和 .customSpring 使用相同 raw value。

    Transition.swift · L81–L99
  • #03-05UIKit布局与动画精选 03-3
    通过私有 _scrollHeartbeat 强制旧系统滚动到高刷新率

    它将字符串拼接为 _scrollHeartbeat。它用 KVC 取出 UIScrollView 内部的 CADisplayLink。它在 iOS 15 的高刷设备上,把该 link 的最小、最大和首选 FPS 都设为屏幕最大值。

    UIViewController+Navigation.m · L293–L317
  • #03-06UIKit布局与动画
    为 iOS 26 弹簧写入私有高刷原因码

    它为 iOS 26 分支换用一组不同弹簧参数。它禁止 overdamping。它通过 KVC 写入 highFrameRateReason = 1048619。它再请求 80–120 FPS,并首选 120 FPS。

    UIKitUtils.m · L68–L84
  • #03-07UIKit布局与动画
    用存储几何量规避 CALayer.frame 的浮点重启动画

    它不直接比较派生的 frame。在无变换时,它从目标 frame 推出 position,并比较图层存储的 position 和 bounds。有变换时才退回比较 frame。

    ContainedViewLayoutTransition.swift · L30–L42
  • #03-08UIKit布局与动画精选 03-4
    从展示层接力中断中的布局动画

    新布局先写入节点模型层。若旧动画仍在运行,它从展示层的当前 frame 取起点。随后显式动画从屏幕当前位置过渡到新模型值。

    ContainedViewLayoutTransition.swift · L234–L248
  • #03-09UIKit布局与动画精选 03-5
    将 frame 拆成两个动画,并汇合完成信号

    animateFrame 用 position 和 bounds 两个 CA 动画表示一个 frame。它分别记录完成状态。任一动画被打断时,它将整个 frame 动画报告为失败。两个成功后,它才报告成功。

    CAAnimationUtils.swift · L510–L535
  • #03-10UIKit布局与动画
    用 additive 位移追赶已更新的模型 frame

    它先设置目标 frame。它再以 additive position 动画,从旧坐标减新坐标的 offset 动到零。视觉上图层从旧位置移动到新位置。

    ContainedViewLayoutTransition.swift · L320–L336
  • #03-11UIKit布局与动画精选 03-6
    分别以不同曲线过渡水平和垂直几何量

    CombinedTransition 保有两个独立 ContainedViewLayoutTransition。它把 frame 拆成 x、y、宽和高四个 additive 动画。它等待四个回调后再完成。

    ContainedViewLayoutTransition.swift · L1975–L1999
  • #03-12UIKit布局与动画
    在 action(forKey:) 中同时禁用隐式动画和追踪挂载

    自定义 CAAction 的 run 为空。图层对任何 action key 都返回该对象。它在 kCAOnOrderIn 和 kCAOnOrderOut 时维护层级状态并触发回调。

    SimpleLayer.swift · L10–L24
  • #03-13UIKit布局与动画
    用一个共享 DisplayLink 服务多种帧率需求

    单例仅保有一个 CADisplayLink。它从活动 request 中取最高 FPS。高刷设备仅在需求超过 60 FPS 时请求 120 FPS。iPad 无条件请求该范围。每个低 FPS request 再通过累计 targetTimestamp - timestamp 节流。

    DisplayLinkAnimator.swift · L89–L94
  • #03-14UIKit布局与动画
    用稳定身份移动 UIKit 子视图,而不是重建

    ChildMap 将 map 对象地址和业务 key 组合为 child ID。更新时它按 ID 找既有 view。顺序变动时,它调整索引并用 insertSubview(_:at:) 重排,而不重新创建 view。

    CombinedComponent.swift · L686–L710
  • #03-15UIKit布局与动画
    让移除中的子视图持续追踪运动 guide

    子组件移除后,框架暂不 removeFromSuperview()。它把 view 放进 disappearingChildViews。带 guide 的消失动画可在后续 body 更新中收到新的 guide 位置。它同时关闭用户交互。

    CombinedComponent.swift · L818–L836
  • #03-16UIKit布局与动画
    用等价性、环境和尺寸组成渲染跳过条件

    宿主缓存组件值、容器尺寸和结果尺寸。环境未变且值相等时,它直接返回缓存 size。局部状态更新则绕过相等性检查,强制重跑 update。

    ComponentHostView.swift · L76–L97
  • #03-17UIKit布局与动画
    将组件上下文附着到 UIView 来保留类型化状态

    类型擦除组件把 ComponentContext 作为关联对象附着到它的 UIView。上下文保存组件、环境、布局结果和 ComponentState。状态对象通过 _updated 回传 transition。

    Component.swift · L80–L101
  • #03-18UIKit布局与动画
    将任意连续变换离散成按设备帧率采样的关键帧

    setTransformAsKeyframes 接受生成任意 CATransform3D 的闭包。它按曲线求进度,并预先生成 transform 数组。它把最终 transform 写入模型层,再提交关键帧动画。

    Transition.swift · L752–L774
  • #03-19UIKit布局与动画
    预渲染并原子缓存圆角位图,再用 blit 合成

    它以角方向和整数半径作为缓存 key。首次绘制一块黑色椭圆象限到 DrawingContext。后续调用通过 context.blt 粘贴该位图。缓存字典使用 Atomic 更新。

    ImageCorners.swift · L40–L64
  • #03-20UIKit布局与动画
    用随机相位的 additive 微位移避免重排项同频摆动

    它给位置和 Z 轴旋转各创建无限重复关键帧。每条动画的 beginTime 随机落在 0–0.24 秒。两条动画都 additive,因此不会覆盖基础布局或变换。

    ShakeAnimation.swift · L67–L86
  • #03-21UIKit布局与动画
    用无布局 CATransformLayer 适配 AsyncDisplayKit

    ASTransformLayer 继承 CATransformLayer,并禁止内容、背景色和布局回调。ASTransformLayerNode 将它作为 AsyncDisplayKit 的 layer block 返回。

    ASTransformLayerNode.swift · L5–L27
  • #03-22UIKit布局与动画
    让模拟器慢动画同时影响 UIKit 和显式 CA 动画

    模拟器构建直接链接 UIAnimationDragCoefficient。显式动画工具读取该系数,并将 CA speed 设为其倒数。真机固定返回 1.0。

    Transition.swift · L5–L18
  • #04-01UIKit列表与滚动精选 04-1
    隐形 UIScrollView 只提供滚动物理

    ListView 隐藏 ListViewScroller。它仍复用 UIScrollView 的拖拽、减速和回弹。它不把内容放进 scroll view。它在 scrollViewDidScroll 中按增量平移自己的 ASDisplayNode。

    ListView.swift · L516–L534
  • #04-02UIKit列表与滚动精选 04-2
    用 20,000pt 假内容高度承载半边未知数据

    当已加载节点只碰到一个数据边界时,ListView 使用 infiniteScrollSize * 2。infiniteScrollSize 在初始化时为 10000.0。当两端都已加载时,它切回真实 completeHeight。

    ListView.swift · L1726–L1745
  • #04-03UIKit列表与滚动精选 04-3
    倒序聊天用父子两次 π 旋转保持文字正向

    历史列表的外层在 rotated 时旋转 π。聊天条目也在 rotated 时旋转 π。两次旋转让条目文字恢复正向。外层旋转把索引 0 的视觉位置变成屏幕底部。

    ChatHistoryListNode.swift · L1025–L1045
  • #04-04UIKit列表与滚动精选 04-4
    事务队列串行化异步布局完成顺序

    队列只启动第一个事务。当前事务完成后,endTransaction() 才启动下一个事务。完成回调不在主线程时,它强制切回主线程。

    ListViewTransactionQueue.swift · L14–L35
  • #04-05UIKit列表与滚动精选 04-5
    布局可异步,节点写入必须回到主队列

    nodeForItem 把条目更新的调度器传给 ListViewItem。默认 async(_:) 使用 .userInteractive 全局队列。QueueLocalObject 把节点访问绑定到主队列。最终 apply 也断言主队列。

    ListView.swift · L1820–L1838
  • #04-06UIKit列表与滚动
    快速滚动时只挂一个可见窗口刷新事务

    enqueuedUpdateVisibleItems 合并多次滚动回调。事务期间,transactionOffset 继续累计。只有偏移仍有变化时,它才补挂下一次刷新。

    ListView.swift · L4618–L4642
  • #04-07UIKit列表与滚动精选 04-6
    用静止条目锚住插入和删除后的视口

    事务可以提供 stationaryItemRange。中间状态记录其中可用条目的旧 minY。新布局完成后,它整体平移所有 frame,使该条目回到旧屏幕位置。

    ListViewIntermediateState.swift · L409–L425
  • #04-08UIKit列表与滚动
    删除动画用占位节点保住消失行的几何空间

    动画删除时,中间布局先移除逻辑节点。它立即放入保留旧 frame 的 .Placeholder。稍后的 replay 阶段可继续让旧节点消失。若旧节点已被复用,它会改用 ListViewTempItemNode。

    ListViewIntermediateState.swift · L823–L843
  • #04-09UIKit列表与滚动
    用 apparentHeight 分离真实布局高度和动画高度

    条目有最终 contentSize,也有逐帧变化的 apparentHeight。动画每帧计算高度差。列表依据条目位于视口上方还是下方,向相反一侧偏移兄弟节点。

    ListViewItemNode.swift · L569–L580
  • #04-10UIKit列表与滚动
    键盘和 inset 动画改为整体 sublayerTransform

    尺寸或 inset 改变后,代码先提交最终 frame。它再给列表 layer 的 sublayerTransform 加一个反向平移动画。所有子节点一起从视觉旧位置过渡到新位置。

    ListView.swift · L3328–L3351
  • #04-11UIKit列表与滚动
    以视口外 500pt 作为软预载窗口

    preloadPages 开启时,ListView 将 invisibleInset 设为 500.0。中间状态只在已加载总高度超过视口加两侧预载区时,移除窗口外节点。

    ListViewIntermediateState.swift · L631–L653
  • #04-12UIKit列表与滚动
    浮动 header 同时处理顶部、边缘和底部吸附

    header 不属于普通行。它根据逻辑分组的上下界计算独立 frame。它暴露吸附距离和归一化因子给 header 自己绘制。完整函数还处理 .bottom 和同一 stackingId 的避让。

    ListView.swift · L4004–L4025
  • #04-13UIKit列表与滚动
    不知道总行高时仍绘制近似滚动条

    自绘滚动条以 44pt 推算未加载行的高度。它再用首尾可见行的真实 frame 和真实高度校正当前视口边界。

    ListView.swift · L4525–L4549
  • #04-14UIKit列表与滚动
    外部速度可注入一段像 UIKit 的减速

    transferVelocity(_:) 用 ConstantDisplayLinkAnimator 驱动指数衰减。它保存亚像素余数,只把整像素写入偏移。ListViewScroller.isDecelerating 在 forceDecelerating 为真时报告减速中。

    ListView.swift · L951–L969
  • #04-15UIKit列表与滚动
    GridNode 给 UIScrollView 换成不响应绘制失效的 layer

    GridNodeScrollerView 指定 GridNodeScrollerLayer 为 backing layer。该 layer 覆盖 setNeedsDisplay() 且不执行任何操作。

    GridNodeScroller.swift · L4–L28
  • #04-16UIKit列表与滚动
    GridNode 先切出 preload 窗口,再创建节点

    GridNode 先从完整 itemLayout 中筛出显示区加 preloadSize。后续 applyPresentationLayoutTransition 只对 presentationItems 创建或更新 GridItemNode。

    GridNode.swift · L815–L849
  • #04-17UIKit列表与滚动
    网格动画先固化屏幕坐标,再做 additive 位移

    布局变更前,代码把项目 frame 减去旧 contentOffset。这得到屏幕坐标。新布局确定后,它计算幸存项目的共同偏差,并以 additive position 动画补偿。

    GridNode.swift · L887–L899
  • #04-18UIKit列表与滚动
    网格用线性分区做“相册式”均衡行

    .balanced 将每个项目的宽高比量化为权重。它按理想行高估算行数。它再用 linearPartitionForWeights 切分连续项目。

    GridNode.swift · L594–L606
  • #04-19UIKit列表与滚动
    稀疏网格把未加载索引视为洞,并定向拉取

    SparseItemGrid.Items 持有总数与局部项目。可见索引没有项目时,代码查找最近 HoleAnchor。它只为第一个洞发出一次 .toLower 加载请求。

    SparseItemGrid.swift · L1305–L1329
  • #04-20UIKit列表与滚动
    稀疏网格按稳定 id 维护可见 layer,而不是 cell 池

    visibleItems 以业务稳定 id 保存 VisibleItem。仍可见的 id 只更新。首次进入窗口的项目才创建 layer 或 view,并进入批量 bindLayers。

    SparseItemGrid.swift · L1095–L1119
  • #04-21UIKit列表与滚动
    用一张长渐变图和 contentsRect 驱动全局 shimmer

    Shimmer 缓存一张 1 × 320 的渐变图。每个占位 layer 只裁剪不同的 contentsRect。它动画化 contentsRect.origin.y,而不是每帧重绘渐变。

    SparseItemGrid.swift · L131–L145
  • #04-22UIKit列表与滚动
    AsyncListComponent 旋转 90° 复用纵向 ListView

    横向模式不新建横向虚拟列表。它旋转整个 ListView 负 π/2,交换宽高,再旋转内部 contentContainer 正 π/2。

    AsyncListComponent.swift · L547–L558
  • #04-23UIKit列表与滚动
    CoreList 用 IMP swizzle 录制 UIKit 物理真值

    CoreList 的 demo 在调试时替换 UIScrollView.setContentOffset:。它还替换私有 selector,以记录橡皮筋输入输出。录制数据供纯 Swift 的 ScrollReplay 回放。

    UIScrollViewPhysicsSwizzler.swift · L108–L132
  • #05-01UIKitASDK 与文本精选 05-1
    不安装系统选择交互,完全自绘选择 UI

    DocumentCanvasView 实现 UITextInput,但不创建 UITextSelectionDisplayInteraction。它保留 UIEditMenuInteraction,并自行绘制光标、选区和两个手柄。它只在一次放大镜拖动期间创建临时交互对象。

    DocumentCanvasView+Interaction.swift · L44–L68
  • #05-02UIKitASDK 与文本精选 05-2
    向键盘投影“虚拟换行”,修复韩文和 CJK 跨段重组

    结构化文档内部使用连续的全局 UTF-16 位置。它向 UITextInput.text(in:) 投影文本时,才在跨顶层段落处插入 \n。它把 emoji 和公式附件还原为可读的替代文本。

    DocumentCanvasView+UITextInput.swift · L40–L77
  • #05-03UIKitASDK 与文本精选 05-3
    把 IME 组合文本和内联预测分成“提交”与“丢弃”两条路径

    setMarkedText 先保存一次组合开始快照。它把输入法文本作为临时文档编辑。光标在标记文本开头时,它把该文本标为预测。finalizeMarkedText 会提交组合文本,却删除预测幽灵文本。

    DocumentCanvasView+MarkedText.swift · L109–L161
  • #05-04UIKitASDK 与文本精选 05-4
    用不可见的 1×1 NSTextAttachment 预留空间,再覆盖真实动态表情视图

    附件自身没有可见内容。共享的透明 spacerImage 强制 TextKit 调用 attachmentBounds。它按 run 字体的 ascender 和 descender 预留正方形字框。随后画布在该框上安装实际动画表情视图。

    EmojiTextAttachment.swift · L19–L62
  • #05-05UIKitASDK 与文本精选 05-5
    旧文本节点用 CTRunDelegate 把自定义 emoji 伪装成一个排版字形

    代码把一个可能多 UTF-16 单元的自定义 emoji 替换为 &。它为这个替身绑定 CTRunDelegate。CoreText 因此得到指定宽度、ascent 和 descent。TextNode 跳过该 run 的字形绘制,并在同一几何位置叠加 InlineStickerIte…

    TextNodeWithEntities.swift · L176–L230
  • #05-06UIKitASDK 与文本
    异步绘制用原子 sentinel 取消旧帧,并在主线程原子回填结果

    每次异步显示递增 _displaySentinel。后台 block 捕获当时值。新的失效请求使旧 block 的检查失败。完成 block 只在未取消时设置 layer.contents。异步任务加入父层事务,事务在主线程同步回填。

    ASDisplayNode+AsyncDisplay.mm · L333–L411
  • #05-07UIKitASDK 与文本
    用“基础层清除 + 剪裁重绘层”实现 spoiler 文本的双层渲染

    普通文本层先绘制整行,再在 spoiler 的逐词矩形上 clear。启用 displaySpoilers 的第二层则只剪裁并绘制这些矩形中的字形。两个层可被特效节点分别控制。

    TextNode.swift · L2567–L2640
  • #05-08UIKitASDK 与文本
    TextKit 2 刻意使用 .selection 几何来修正 RTL 光标冻结

    光标位置查询不使用 TextKit 2 的 .standard segment。它使用 .selection segment。空 RTL 段落还手动把光标放到容器右端。

    BlockLayout.swift · L138–L157
  • #05-09UIKitASDK 与文本
    关闭 UITextView 滚动时仍让它认为滚动已开启

    ASPanningOverriddenUITextView 对外报告“不可滚动”,却总是把父类 scrollEnabled 设为 YES。它阻断自身 pan、contentSize 和 contentOffset 更新。

    ASEditableTextNode.mm · L69–L135
  • #05-10UIKitASDK 与文本
    自定义截断 token 采用两次 CTLineCreateTruncatedLine,防止 token 被吞掉

    TextNode 先测量 token 宽度。它首次按全宽请求 CoreText 截断。它检查生成 run 的源 range 是否已抵达原字符串末尾。若没有 token,它把可用宽度减去 token 宽度再截断一次。显式换行截断则把 token 保存为 additionalTrailingLin…

    TextNode.swift · L1991–L2055
  • #05-11UIKitASDK 与文本
    手写 CoreText 逐行“切口”排版,给头像或气泡角留出空间

    布局器对每一行判断其垂直范围是否碰到左上或右上切口。命中的行缩窄可用宽度。左上切口还平移行 x。底部切口在布局完成后再决定扩宽最后一行还是增加高度。

    TextNode.swift · L1803–L1855
  • #05-12UIKitASDK 与文本
    layer-backed 节点用弱代理修复 CALayer.delegate 悬空指针

    layer-backed 节点不把自身直接交给 CALayer.delegate。它创建弱目标代理,并用 associated object 强持有代理。layer 比 node 活得更久时,代理把回调安全地吞掉。

    ASDisplayNode.mm · L582–L609
  • #05-13UIKitASDK 与文本
    以字符串拆分引用私有自动纠错提示类,并禁用私有替换菜单项

    编辑文本视图阻止 _promptForReplace: 和部分格式菜单。点击自动纠错提示时,它调用 dropAutocorrection。该方法先 unmarkText,再把选择区前移一个字符又还原,以刷新 UIKit 状态。

    ASEditableTextNode.mm · L141–L175
  • #05-14UIKitASDK 与文本
    复用链接高亮轮廓作为 mask,实现只在文字形状中流动的 shimmer

    TextLoadingEffectView 把 LinkHighlightingNode 生成的跨行圆角轮廓设为 UIView mask。它移动一张窄的模板渐变图片。内容层和描边层各有一张渐变图。

    TextLoadingEffect.swift · L49–L59
  • #05-15UIKitASDK 与文本
    RichText spoiler 用显示属性隐藏字形,并以稳定身份池化粒子层

    画布按选择区决定 spoiler 是否显示。BlockLayout 用 TextKit 2 的 rendering attribute 把隐藏字形绘制为透明,而不改 storage。粒子视图的 key 由布局对象身份和段内 run 索引组成,不用易变的全局字符偏移。

    DocumentCanvasView+Spoilers.swift · L37–L55
  • #05-16UIKitASDK 与文本
    Markdown 粘贴拆成两个 run-loop 撤销组,并抑制中间闪屏

    当宿主把纯文本 Markdown 转为富文本文档时,编辑器先粘贴纯文本。下一次主 run-loop 再替换为富文本。第一阶段不通知宿主,因此用户看不到中间纯文本。

    DocumentCanvasView+Clipboard.swift · L198–L224
  • #05-17UIKitASDK 与文本
    链接高亮把相邻行矩形缝成一个连续的圆角轮廓

    高亮绘制器先分离不相邻矩形。它把近似相同的左右边吸附。然后沿多行矩形的外周构造一个 CGPath,并在行宽变化处加入连接圆角。

    LinkHighlightingNode.swift · L59–L195
  • #05-18UIKitASDK 与文本
    文本布局比较所有影响条件,命中后直接复用旧 TextNodeLayout

    asyncLayout 捕获当前 layout。它检查尺寸、最大行数、截断方式、切口、对齐、行距、背景色和 attributed string。完全相同时直接返回旧 layout。否则计算新布局,并只在变化时 setNeedsDisplay。

    TextNode.swift · L2854–L2906
  • #05-19UIKitASDK 与文本
    将可 tint 的字和不可 tint 的彩色 emoji 分离为两个重叠文本节点

    每个状态保留两份同样的文本布局。一份只画普通文字。另一份只画彩色 emoji。它们重叠放置。updateTintColor 只改变文字层的 layer tint。

    MultiScaleTextNode.swift · L6–L25
  • #05-20UIKitASDK 与文本
    AsyncDisplayKit 的 TextKit 分叉用自定义容器保留引文缺口,并跳过自定义 emoji 字形

    分叉实现 ASCustomTextContainer,将引文首行缩短 100pt。它还实现 ASCustomLayoutManager,遇到 Attribute__CustomEmoji 时不调用父类绘制该 glyph。

    ASTextKitComponents.mm · L26–L80
  • #06-01UIKit导航、手势与上下文菜单精选 06-1
    用私有 _UIPortalView 把同一内容跨层显示

    代码创建一个 Portal 视图。它镜像源视图的位置和变换。它不镜像 alpha,也不接收命中测试。PortalView 会把源视图赋给 sourceView,并强制插回原层级索引。

    PortalView.swift · L4–L35
  • #06-02UIKit导航、手势与上下文菜单精选 06-2
    用 Portal 暂存源内容,并在动画末尾明确归还

    代码先把真实内容重挂到 PortalSourceView。它再把 Portal 镜像放入覆盖层。动画完成时,代码调用 settle,把真实内容重挂回菜单偏移容器或原父视图。

    ContextControllerExtractedPresentationNode.swift · L175–L196
  • #06-03UIKit导航、手势与上下文菜单精选 06-3
    给透明背景塞入 1×1 图片,修复“看得见但点不到”的菜单外部点击

    菜单背景层即使颜色为 .clear,也会拥有完全透明的 contents。这使背景层继续参与触摸投递。它因此可以接住菜单外的点击并关闭菜单。

    ContextSourceContainer.swift · L410–L423
  • #06-04UIKit导航、手势与上下文菜单
    提取到未缩放覆盖层前,补回祖先缩放

    代码把源视图的窗口可见宽度除以模型宽度。它把检测到的缩放施加到提取容器。它还保存该缩放,供退场补偿使用。

    ContextControllerExtractedPresentationNode.swift · L776–L800
  • #06-05UIKit导航、手势与上下文菜单
    在退场中用 presentation layer 重定向落点

    源消息在菜单退场时改高度,代码不重新跑整套布局。它读取 presentation() 与模型层的残差。它用稳定 key 替换一条 bounds.origin.y 的加法动画,并只使用剩余时长。

    ContextControllerExtractedPresentationNode.swift · L2271–L2295
  • #06-06UIKit导航、手势与上下文菜单精选 06-4
    全屏右滑返回先查命中视图链,再用方向阈值裁决

    识别器从 hitTest 结果向上查找。子视图可声明“有横向手势”或“严格禁止”。普通横向内容会禁用屏幕中央返回。边缘返回仍可保留。移动超过 10 pt 后,识别器才固定横向主方向。

    InteractiveTransitionGestureRecognizer.swift · L106–L128
  • #06-07UIKit导航、手势与上下文菜单精选 06-5
    手工驱动返回进度,并同步键盘和状态栏

    右滑开始时,导航容器预先布局底层控制器。每个交互进度会移动键盘左边界。进度达到 0.3 时,状态栏风格提前切到目标页。结束条件为速度超过 1000,或进度超过 0.2。

    NavigationContainer.swift · L236–L313
  • #06-08UIKit导航、手势与上下文菜单
    用像素对齐的手工几何实现导航视差和阴影

    协调器把顶部页、底部页、遮罩和阴影拆成独立节点。顶部页使用屏幕像素对齐。非平面容器让底部页只走 30% 距离,形成视差。初始化时它还持有最大帧率的 SharedDisplayLinkDriver 链接。

    NavigationTransitionCoordinator.swift · L158–L187
  • #06-09UIKit导航、手势与上下文菜单
    转场时临时重挂两个导航栏,再用裁剪容器切开

    容器保存两个导航栏的原父节点。它在转场时把两个导航栏重挂进独立裁剪节点。layout() 根据进度移动顶部裁剪区域,并收窄底部可见宽度。完成后它再归还导航栏。

    NavigationBarTransitionContainer.swift · L22–L68
  • #06-10UIKit导航、手势与上下文菜单
    上下文长按先避开系统左边缘,再主动取消整棵手势树

    ContextGesture 会拒绝窗口左侧 8 pt 内的按压。延迟后它用显示链接动画按压进度。真正激活时,它取消窗口中的同类手势,并沿父视图链把其他手势置为 .failed。

    ContextGesture.swift · L130–L178
  • #06-11UIKit导航、手势与上下文菜单
    一个识别器同时处理点击、双击、长按、右键和按住

    该自定义识别器根据命中位置选择等待策略。它区分单击、双击、长按、二级点击和 hold。hold 分支会先 prepareTap()。长按触发时还会取消所有同类识别器,并结束祖先滚动视图的 pan。

    TapLongTapOrDoubleTapGestureRecognizer.swift · L181–L239
  • #06-12UIKit导航、手势与上下文菜单
    Peek 从源矩形弹出,并按激活模式切换模糊策略

    Peek 根据源矩形中心计算加法位置弹簧。已出现的内容会按源宽度与内容宽度的比例缩放进入。构造阶段中,拖动激活使用 UIVisualEffectView 模糊。按压激活使用半透明纯色遮罩。构造阶段还预热了触感。

    PeekControllerNode.swift · L271–L300
  • #06-13UIKit导航、手势与上下文菜单
    仅在最内层滚动视图位于顶部时才认可下滑关闭

    识别器递归从触点寻找 UIScrollView。它只在滚动位置不低于顶部 inset 时保留资格。它超过 5 pt 的横向位移就失败。下移超过 2 pt 才开始。

    SwipeToDismissGestureRecognizer.swift · L48–L107
  • #06-14UIKit导航、手势与上下文菜单
    最小化后延迟把活控制器换成快照

    最小化卡片先承载真实控制器视图。0.45 秒后,它抓取内容快照。它移除真实视图,只留下快照和自有标题栏。

    MinimizedContainer.swift · L125–L137
  • #06-15UIKit导航、手势与上下文菜单
    用私有 CAFilter 给快照补背景模糊

    卡片快照比例不匹配容器时,代码另抓一个快照。它给该快照的 layer.filters 设置高斯模糊,再放到清晰快照下方填满空白区域。

    MinimizedContainer.swift · L272–L295
  • #06-16UIKit导航、手势与上下文菜单
    用 3D 变换把最小化页面堆成可滚动卡片组

    展开后,每张卡片按其滚动位置计算 final3dTransform。高亮卡片额外旋转 0.04。关闭中的卡片单独接受横向偏移。折叠时,非顶卡按比例缩小并下沉。

    MinimizedContainer.swift · L935–L1017
  • #06-17UIKit导航、手势与上下文菜单
    iPad 分栏只把真实屏幕边缘的安全区给对应窗格

    分栏宽度被限制在屏宽的三分之一到二分之一之间。master 去掉右安全区。detail 去掉左安全区。它还为两个窗格分别生成状态栏高度的滚动到顶代理区域。

    NavigationSplitContainer.swift · L73–L106
  • #06-18UIKit导航、手势与上下文菜单
    触感生成器按样式缓存,并在手势开始时预热

    封装延迟创建选择、通知和 impact 生成器。impact 生成器按样式缓存。长按 hold 和 Peek 构造期会先调用 prepareTap()。click05、click06 和 veryLight 在 iOS 13 以后用低强度模拟。

    HapticFeedback.swift · L29–L39
  • #06-19UIKit导航、手势与上下文菜单
    画廊退场用源快照接棒,并等待三个动画完成

    画廊关闭时,代码把源节点提供的 copyView 插到内容下方。快照从全屏内容的几何变换到源位置。真实全屏节点同时淡出并缩放。只有位置、缩放和快照动画都完成,代码才移除快照并回调。

    ChatAnimationGalleryItem.swift · L276–L312
  • #06-20UIKit导航、手势与上下文菜单
    反应面板通过顶部过拉扩展,并主动取消滚动 pan

    菜单内容不足一屏时,代码只允许顶部过拉。过拉距离先乘以 0.35 做阻尼。超过 30 pt 时,它取消滚动视图自身的 pan,并把反应面板扩展为新状态。

    ContextControllerExtractedPresentationNode.swift · L590–L631
  • #07-01UIKit视觉特效与玻璃精选 07-1
    直接驱动 iOS 26 的私有 Liquid Lens

    代码按运行时类名创建 _UILiquidLensView。 它把抬起内容和挖空内容交给私有 Lens。 它把 Lens 放在背景和交互内容之间。

    LiquidLensView.swift · L198–L257
  • #07-02UIKit视觉特效与玻璃精选 07-2
    为私有 Lens 补齐弹跳刷新和位置动画

    代码先发起 bounds 动画。 它随后清空私有 Lens 的残留动画。 它再写入最终 bounds。 它还补一个随宽度变化的加法 position 动画。

    LiquidLensView.swift · L375–L415
  • #07-03UIKit视觉特效与玻璃精选 07-3
    私有 Lens 动画期间强制 120 Hz

    代码为每个 morph 申请共享显示链路。 它把支持 ProMotion 的屏幕锁定为 120 Hz。 它使用引用计数合并并发 morph。 它还用一秒超时释放未回调的申请。

    LiquidMorphTransition.swift · L75–L145
  • #07-04UIKit视觉特效与玻璃
    没有私有 Lens 时,用亮度转 Alpha 模拟挖空玻璃

    代码把白色容器作为内容 mask。 它给 mask 加私有 luminanceToAlpha 滤镜。 它以可拉伸圆角胶囊图控制挖空区域。

    LiquidLensView.swift · L267–L296
  • #07-05UIKit视觉特效与玻璃
    用 _UIPortalView 复制远端视图,而非截图

    代码运行时解析并实例化 _UIPortalView。 它同步源视图的位置和变换。 它禁用命中测试转发和 portal 命中测试。

    UIKitUtils.m · L211–L235
  • #07-06UIKit视觉特效与玻璃
    Portal 重绑后原位置重插,迫使渲染刷新

    代码先重新设置 sourceView。 它再把 portal 重插回原来的下标。 它在 UIView 和纯 CALayer 两种宿主中都处理。

    PortalView.swift · L15–L28
  • #07-07UIKit视觉特效与玻璃
    聊天气泡真正“透出”同一张动态壁纸

    每种气泡类型都有各自的壁纸 portal 源。 气泡节点只显示相同源的 PortalView。 它不在每个气泡内重新绘制壁纸。

    WallpaperBackgroundNode.swift · L1988–L2023
  • #07-08UIKit视觉特效与玻璃
    拆解 UIVisualEffectView 内部滤镜,获得可控导航模糊

    代码从公开 UIVisualEffectView 取得系统内部层。 它隐藏内部视觉子视图。 它只保留 gaussianBlur 和可选 colorSaturate。

    NavigationBackgroundView.swift · L62–L108
  • #07-09UIKit视觉特效与玻璃精选 07-4
    用 CABackdropLayer 和颜色矩阵手搓旧版玻璃

    代码创建私有 CABackdropLayer。 它先应用 backdrop-aware 颜色矩阵。 它再应用小半径高斯模糊。

    LegacyGlassView.swift · L7–L13
  • #07-10UIKit视觉特效与玻璃精选 07-5
    私有 CAMeshTransform 让 backdrop 边缘像玻璃一样折射

    代码为圆角矩形生成位移网格。 它把网格写到 backdrop 的私有 meshTransform 键。 尺寸变化时,它显式插值旧网格和新网格。

    LegacyGlassView.swift · L198–L230
  • #07-11UIKit视觉特效与玻璃
    把可变模糊半径编码进一张一像素宽的 mask

    代码生成一像素宽的黑白渐变图。 它拉伸该图覆盖任意宽度。 它把图作为私有 variableBlur 的 inputMaskImage。

    EdgeEffect.swift · L693–L698
  • #07-12UIKit视觉特效与玻璃
    波纹优先用位移贴图,缺失时回退网格变形

    代码优先生成低分辨率的位移贴图。 它根据性能自适应贴图分辨率。 没有位移层时,它回退私有 mesh 变形。

    SpaceWarpView.swift · L888–L912
  • #07-13UIKit视觉特效与玻璃
    自绘 Liquid Glass 用 Metal 输出私有位移图的 RG 编码

    Shader 在每个形状边缘计算向内采样偏移。 它将 X 和 Y 偏移编码进红绿通道。 中性值 0.5 表示不位移。

    LiquidGlassShapes.metal · L497–L524
  • #07-14UIKit视觉特效与玻璃
    每帧自绘玻璃,但离屏后及时释放昂贵 Surface

    代码为内容、遮罩和位移图分别渲染专属 Surface。 源码注释说明录音 blob 的 3x Surface 约为 5.5 MB。 离开窗口时,策略延迟到当前 run loop 末尾检查。

    LiquidGlassShapesLayer.swift · L358–L389
  • #07-15UIKit视觉特效与玻璃
    Spoiler 用私有 CAEmitterBehavior 实现闪烁和吸引力

    代码通过 IMP 调用私有 emitter 工厂。 调用方可以取得闪烁和吸引器行为对象。 公开 CAEmitterLayer 仍负责粒子发射。

    SpoilerDustView.swift · L239–L250
  • #07-16UIKit视觉特效与玻璃
    点按揭示 spoiler 时,反向径向 mask 让粒子从触点爆开

    代码生成中心透明、外部不透明的 mask。 半径同时受 10 pt 和文本区域尺寸限制。 该 mask 作为粒子揭示区域的触点孔洞。

    SpoilerDustView.swift · L183–L199
  • #07-17UIKit视觉特效与玻璃
    动态渐变不是每帧算 shader,而是预烘 8 个阶段并动画 contents

    代码先生成一串渐变位图。 它用 CAKeyframeAnimation 动画 layer 的 contents。 它把同一关键帧同步给原色和降饱和 clone。

    SoftwareGradientBackground.swift · L637–L675
  • #07-18UIKit视觉特效与玻璃
    多个动态壁纸用共享阶段保持相位一致

    代码把阶段循环限制在 0...7。 它把发起者之外的背景同步到同一 phase。 它用管道广播而不是各自用时间戳计算。

    SoftwareGradientBackground.swift · L770–L794
  • #07-19UIKit视觉特效与玻璃
    Shimmer 用超宽 mask 和加法 position 无限滚动

    mask 比实际内容宽两段完整行程。 动画只对 position.x 做加法位移。 布局变化时,它先更新静态基准位置再重启动画。

    ShimmeringMaskView.swift · L61–L81
  • #07-20UIKit视觉特效与玻璃
    气泡撕裂边缘用“过冲一像素”消除平铺接缝

    黑色撕裂带超出气泡边界。 白色边缘图却避开 overhang 和尾巴预留列。 上边图刻意少一个屏幕像素高。

    ChatMessageBackgroundTear.swift · L289–L314
  • #08-01UIKit渲染与性能精选 08-1
    用一张 IOSurface 图集承载多个独立图层

    引擎把多个 MetalEngineSubjectLayer 分配到同一 IOSurface。每个图层只用 contentsRect 展示自己的子矩形。引擎只在图集变更时重设 layer.contents。

    MetalEngine.swift · L920–L976
  • #08-02UIKit渲染与性能
    为 EDR 大层放弃固定图集,避免 32 MB 空耗

    普通小层优先放入固定表面。尺寸达到 1024 的层改用贴合尺寸的表面。rgba16Float 也使用对齐后的专用尺寸。

    MetalEngine.swift · L920–L975
  • #08-03UIKit渲染与性能精选 08-2
    只在首个脏 subject 时请求一次绘制

    引擎为每个 subject 赋稳定内部编号。它在一个显示周期内去重脏 subject。只有队列从空变为非空时才调用根层的 setNeedsDisplay()。

    MetalEngine.swift · L1043–L1071
  • #08-04UIKit渲染与性能精选 08-3
    三缓冲私有纹理,并在 GPU 完成后归还

    PooledTexture 预建三个 GPU 私有纹理。每次渲染取一个空闲纹理。引擎在 command buffer 完成回调中再调用 free()。

    MetalEngine.swift · L217–L268
  • #08-05UIKit渲染与性能精选 08-4版本差异
    用 Objective-C 方法替换规避 MTLBinaryArchive 崩溃

    保存 Metal 二进制归档前,代码临时登记目标路径。它替换 FileManager 的公开 Objective-C 方法。失败时,它把 NSError 保活到 serialize(to:) 返回。

    MetalBinaryArchiveSerialization.swift · L22–L90
  • #08-06UIKit渲染与性能
    用保存标记和 rename(2) 防止缓存崩溃循环

    管线突发创建后延迟一秒合并保存。保存前创建 .saving 标记。它先写随机临时文件,再用原子 rename(2) 替换旧归档。

    MetalPipelineCache.swift · L233–L309
  • #08-07UIKit渲染与性能
    一个定时器驱动整组 DCT 动画,并按电量降到 30 FPS

    一个 DCTMultiAnimationRendererImpl 用一个 timer 驱动所有活动项。没有活动项时立刻销毁 timer。低电量或 CPU 核数不超过 2 时,frameSkip 为 2。

    DCTMultiAnimationRendererImpl.swift · L893–L934
  • #08-08UIKit渲染与性能
    相同贴纸只解码一次,再扇出到多个弱目标

    默认 uniqueId 为 0。相同资源和尺寸命中同一个 ItemAnimationContext。该上下文把同一帧分发给多个弱引用目标。调用者可用 unique 强制独立时间线。

    DCTMultiAnimationRendererImpl.swift · L686–L743
  • #08-09UIKit渲染与性能
    用两条固定后台队列并行解码,再批量回主线程提交

    渲染器按稳定的 queueAffinity 将帧任务分到两条固定队列。两个分区完成后,代码再把所有图层更新闭包投到主线程。

    DCTMultiAnimationRendererImpl.swift · L1026–L1077
  • #08-10UIKit渲染与性能
    DCT 缓存以 YUVA 平面存储关键帧和差分帧

    缓存把帧转为 Y、U、V、Alpha 四个 DCT 系数平面。非关键帧存储相对前帧的系数差。每帧带关键帧标记和四个平面长度。

    DCTAnimationCacheImpl.swift · L366–L445
  • #08-11UIKit渲染与性能
    以“单飞”请求共享缓存构建,并在无人订阅时中止

    缓存键相同的读取者挂到同一个 ItemContext。只有首个读取者启动抓取和压缩。后续读取者订阅结果。最后一个订阅者离开时,代码 dispose 抓取并把 writer 标为取消。

    DCTAnimationCacheImpl.swift · L1343–L1423
  • #08-12UIKit渲染与性能
    从更高分辨率缓存即时派生较小缓存

    请求尺寸缺失时,缓存可读取更高分辨率版本。它直接缩放四个 YUVA 平面,并写回所需尺寸的 DCT 缓存。

    DCTAnimationCacheImpl.swift · L1139–L1186
  • #08-13UIKit渲染与性能
    DrawingContext 将位图缓冲的所有权直接交给 CGImage

    DrawingContext 采用统一的设备行对齐。它让 CGContext 直接画入 ASCGImageBuffer。生成 CGImage 时,createDataProviderAndInvalidate() 转移缓冲所有权。

    GenerateImage.swift · L619–L700
  • #08-14UIKit渲染与性能
    用原位字节创建带 IOSurface 属性的 CVPixelBuffer

    代码不复制 DrawingContext 的位图。它将同一地址传给 CVPixelBufferCreateWithBytes,并以 release callback 管理 ASCGImageBuffer 生命周期。options 请求 IOSurface 属性。

    GenerateImage.swift · L702–L724
  • #08-15UIKit渲染与性能
    GIF 网格使用一个显示链路,并只预读两帧

    多个 GIF 视频目标共用 SharedDisplayLinkDriver。每个目标最多补到两帧。所有预读都在 sharedQueue 完成。isRendering 阻止同一时间重复发起解码批次。

    BatchVideoRenderingContext.swift · L268–L372
  • #08-16UIKit渲染与性能
    用墙钟重放 CMSampleBuffer,而不信任各层播放时钟

    批处理器用 CACurrentMediaTime() 作为共同墙钟。它比较已显示帧的 PTS 和真实经过时间。PTS 回退时按上一帧 duration 判断循环边界。

    BatchVideoRenderingContext.swift · L53–L87
  • #08-17UIKit渲染与性能
    小尺寸 GIF 禁用去块滤波,并给不可解码文件熔断

    网格 GIF 约以 93 pt 显示时,解码器跳过去块滤波。若整个文件未产出有效帧,或出现不应发生的等待状态,代码永久标记失败。

    BatchVideoRenderingContext.swift · L90–L162
  • #08-18UIKit渲染与性能
    Lottie 缓存固定每隔一帧栅格化一次

    Lottie 缓存无条件使用 frameSkip = 2。它把两帧的时间合并进一张位图。代码也在后台 writer 队列生成,并随时响应取消。

    LottieAnimationCache.swift · L10–L60
  • #08-19UIKit渲染与性能
    Subcodec 把多个精灵合为一条编码流,再切回各自区域

    相同尺寸的贴纸进入 SurfaceGroup。每个贴纸占一个 slot。任一 slot 前进后,surface 只发出一条合成 NAL 流。代码解码最近帧,再按 color/alpha 区域切回每个目标。

    SubcodecMultiAnimationRendererImpl.swift · L206–L288
  • #08-20UIKit渲染与性能
    尘埃效果把粒子状态更新和绘制都留在 GPU

    每个尘埃 item 在 GPU buffer 中保存粒子。compute shader 初始化并更新状态。渲染阶段用六顶点四边形做实例绘制,不把粒子位置读回 CPU。

    DustEffectLayer.swift · L295–L362
  • #08-21UIKit渲染与性能
    JPEG 解码选择 TurboJPEG 快速路径,并让 vImage 不分块

    代码把行宽对齐到 32 字节。它要求 TurboJPEG 使用快速 DCT 和快速上采样。随后用两次 vImage 操作转为应用所需的通道顺序。

    MozjpegBinding.mm · L735–L811
  • #08-22UIKit渲染与性能
    复用 AVSampleBufferDisplayLayer,并禁用隐式动作

    takeSampleBufferLayer() 从全局原子池取图层。归还时在主线程 flush、清图并重置 transform。子类对所有 layer action 返回空 CAAction。

    SampleBufferPool.swift · L6–L65
  • #09-01UIKit聊天界面精选 09-1
    先交接第一响应者,再释放旧输入框

    代码先让新输入节点成为第一响应者。代码随后才让旧节点辞去第一响应者。

    ChatTextInputPanelNode.swift · L1124–L1141
  • #09-02UIKit聊天界面
    文本发送动画分离横纵轴曲线

    代码把横向和纵向位移拆成两条动画。代码再把输入框背景和内容交给目标气泡。

    ChatMessageTransitionNode.swift · L689–L713
  • #09-03UIKit聊天界面精选 09-2
    媒体快照以反向缩放交接给气泡

    代码把媒体输入快照放进过渡容器。代码同时缩放目标内容和反向缩放快照层。

    ChatMessageTransitionNode.swift · L986–L1010
  • #09-04UIKit聊天界面精选 09-3
    录音圆球自己飞入语音气泡

    代码不截取录音圆球的快照。代码让圆球本身飞向消息内的播放按钮位置。

    ChatMessageTransitionNode.swift · L904–L928
  • #09-05UIKit聊天界面精选 09-4
    媒体拼图按四个角独立决定合并形状

    代码为拼图的四个角分别计算邻接状态。代码还区分普通合并和气泡合并。

    ChatMessageBubbleItemNode.swift · L3353–L3375
  • #09-06UIKit聊天界面
    反应区会改变气泡的底部合并边

    代码在有反应且不是预览时覆盖底部合并状态。入站消息取左边,出站消息取右边。

    ChatMessageBubbleItemNode.swift · L2395–L2415
  • #09-07UIKit聊天界面
    输入框以文本测量值增长,但最多十二行

    代码用可用宽度测量富文本高度。代码把高度限制在 1 到 12 行,并返回溢出标记。

    ChatTextInputPanelNode.swift · L1410–L1429
  • #09-08UIKit聊天界面精选 09-5
    快速点按录音键改模式,长按才开始录音

    代码延迟 0.19 秒才调用 beginRecording。用户在定时器存活时松手,代码切换录音模式。

    ChatTextInputMediaRecordingButton.swift · L519–L550
  • #09-09UIKit聊天界面
    录音手势把横移、取消位移和锁定拆开上报

    代码把控制条横向位移和取消提示位移分别保存。代码在锁定事件中上报锁定,并允许锁定。

    ChatTextInputMediaRecordingButton.swift · L553–L575
  • #09-10UIKit聊天界面
    媒体面板只在允许时自动切回文字输入

    输入面板请求文字模式时先询问当前实体键盘。代码只把 .media 状态改成 .text。

    ChatControllerNode.swift · L1035–L1051
  • #09-11UIKit聊天界面
    实体键盘把隐藏输入框高度当作组件输入

    代码缓存完整布局状态。代码把被隐藏的文字输入面板高度传给实体键盘组件。

    ChatEntityKeyboardInputNode.swift · L1814–L1836
  • #09-12UIKit聊天界面
    表情搜索覆盖层只在首次出现时立即建树

    代码把搜索放在现有键盘上的独立 searchView。首次出现时淡入并按顶部面板位移,退出后淡出再移除。

    EntityKeyboard.swift · L813–L842
  • #09-13UIKit聊天界面精选 09-6
    列表更新前只采样一次已稳定的偏移

    代码在准备列表事务时读取一次 settledContentOffsets。代码用同一份样本判断是否贴近最新消息和是否填满屏幕。

    ChatHistoryListNode.swift · L2515–L2534
  • #09-14UIKit聊天界面
    未读分割条离屏时强制重新聚焦

    代码遍历现有项目节点。它找到 ChatUnreadItemNode 后立即确保该节点可见。

    ChatHistoryListNode.swift · L2957–L2975
  • #09-15UIKit聊天界面精选 09-7
    回复跳转不能伪装成新消息到达

    代码在创建内容节点前记录真正的新到达消息。代码明确排除加载另一段历史,包括回复跳转。

    ChatHistoryListNode.swift · L4095–L4115
  • #09-16UIKit聊天界面
    跳转请求同时携带高亮、引用和回复设置

    代码把高亮、引用片段、回复主题和滚动锚点封装进一次 .Scroll 请求。每次请求还生成新的位置标识。

    ChatHistoryListNode.swift · L3856–L3879
  • #09-17UIKit聊天界面精选 09-8
    用标识符替换编辑器格式菜单,保留系统菜单其余项

    代码移除编辑器自带的格式子菜单。代码按固定位置插入聊天输入框自己的格式菜单。

    ChatTextInputPanelNode.swift · L5201–L5220
  • #09-18UIKit聊天界面
    录音期间禁用交互式返回手势

    代码注册窗口平移识别器后,提供交互式页面转场的动态禁用回调。录音状态会令回调返回 true。

    ChatControllerNode.swift · L1098–L1120
  • #10-01UIKit媒体、Stories 与个人页
    用运行时调用私有 CAEmitterBehavior

    代码动态组装类名 CAEmitterBehavior。代码动态组装 selector behaviorWithType:。随后它经 KVC 为粒子设置 colorOverLife 和 simpleAttractor 行为。

    UIKitUtils.swift · L935–L943
  • #10-02UIKit媒体、Stories 与个人页精选 10-1
    个人页用私有 _verticalVelocity 把第二次下拉变成头像画廊

    代码把 "_" 和 "verticalVelocity" 拼成 _verticalVelocity。它通过 KVC 读取 UIScrollView 的私有纵向速度。首次下拉到 -32 时展开头像。展开后再次下拉会触发画廊。

    PeerInfoScreen.swift · L5984–L6021
  • #10-03UIKit媒体、Stories 与个人页精选 10-2
    圆视频把 NV12 的两个平面零拷贝映射到 Metal

    相机输入的 NV12 数据不经过 CGImage。Y 平面映射为 .r8Unorm。CbCr 平面映射为 .rg8Unorm。计算着色器随后完成颜色转换、镜像和裁切。

    RoundVideoFrameProcessor.swift · L467–L516
  • #10-04UIKit媒体、Stories 与个人页精选 10-3
    圆视频先低分辨率模糊,再在同一命令缓冲区合成

    代码以 30 fps 离散选择装饰图集帧。代码先写入小尺寸纹理。代码对小纹理执行模糊。后续合成器读取模糊纹理、原始 YUV、图集和水印。

    RoundVideoFrameProcessor.swift · L332–L385
  • #10-05UIKit媒体、Stories 与个人页精选 10-4
    圆视频用有上限的 CVPixelBufferPool 施加背压

    输出池最少保留四个缓冲。代码主动申请四个缓冲后立即释放引用。池的分配阈值为六。isRendering 还保证同一时刻只处理一帧。

    RoundVideoFrameProcessor.swift · L218–L272
  • #10-06UIKit媒体、Stories 与个人页精选 10-5
    处理完成前显式保活像素缓冲和纹理

    代码保留输入、输出 CVPixelBuffer 和 CVMetalTexture。它直到 GPU 命令完成后才结束这些对象的生命周期。回调也切回相机的 mediaQueue。

    RoundVideoFrameProcessor.swift · L406–L434
  • #10-07UIKit媒体、Stories 与个人页
    媒体编辑器用分块直方图生成局部增强 LUT

    代码为每个网格块计算 MPSImageHistogram。代码将每块直方图写进共享私有缓冲。计算着色器再生成一组 LUT。

    EnhanceRenderPass.swift · L154–L188
  • #10-08UIKit媒体、Stories 与个人页
    增强滤镜复用私有输出纹理,并跳过无用清屏

    滤镜只在首次执行时创建输出纹理。纹理使用 .private 存储。渲染通道使用 .dontCare 作为加载动作。

    EnhanceRenderPass.swift · L199–L245
  • #10-09UIKit媒体、Stories 与个人页
    切换嵌入式相机时冻结旧预览并叠加圆形毛玻璃

    切相机前先取 snapshotView。代码把快照留在原位。代码再以翻转动画显示圆形深色模糊层。切换完成后分别淡出毛玻璃和快照。

    DrawingStickerEntityView.swift · L558–L595
  • #10-10UIKit媒体、Stories 与个人页
    GalleryUI 用双过渡视图同步缩略图与全屏圆角

    画廊创建两个过渡视图。一个加入上层 transition surface。一个留在项目自身。回调收到源尺寸、目标尺寸、屏幕圆角和 progress。

    UniversalVideoGalleryItem.swift · L2565–L2589
  • #10-11UIKit媒体、Stories 与个人页
    Stories 退场时把克隆层递归设回屏幕光栅倍率

    fixScale 会将层及所有子层的 rasterizationScale 设为 UIScreenScale。退场时故事内容和目标头像各有一份过渡克隆。代码在跨父视图搬移主克隆前换算全局坐标。

    StoryItemSetContainerComponent.swift · L2382–L2407
  • #10-12UIKit媒体、Stories 与个人页
    Stories 把全部暂停原因收口为播放模式函数

    播放模式由缩放、下拉、菜单、输入、录音、贴纸和隐私页共同决定。上下文菜单或文本选择会返回 .blurred。其余干扰状态通常返回 .pause。

    StoryItemSetContainerComponent.swift · L1430–L1479
  • #10-13UIKit媒体、Stories 与个人页
    Stories 明确规定点击让位于平移,并只和滚动平移共存

    点击识别器会等待平移失败。故事主平移只与滚动视图或当前观看列表的平移同时识别。其他组合被拒绝。

    StoryItemSetContainerComponent.swift · L910–L937
  • #10-14UIKit媒体、Stories 与个人页
    头像画廊只预加载当前项前后两张

    头像轮播窗口固定为当前项前后各两项。代码以资源 ID 复用节点。只有中心项在要求时同步加载。首项还可限制为全尺寸资源。

    PeerInfoAvatarListNode.swift · L1649–L1673
  • #10-15UIKit媒体、Stories 与个人页
    切前后摄时暂存首帧,并校正录制时间线

    录制器用 videoTimelineOffset 重映射展示时间。切相机后的首帧先被 heldFrame 暂存。解析后,首帧可越过正常的停止边界检查。

    VideoRecorder.swift · L277–L317
  • #11-01非 UI非 UI 内核精选 11-1共享模块间接证据
    用双重虚拟映射消除环形缓冲区回绕

    它先预留两倍页对齐地址空间。它再把第一段物理页映射到紧邻的第二段。任何跨越尾部的逻辑连续区间都变成连续指针。

    RingBuffer.m · L33–L105
  • #11-02非 UI非 UI 内核精选 11-2
    用“所有权栅栏”和三次阀门保护共享 SQLite

    SqliteValueBox 只让主应用的读写连接删除数据库。它将连续可计数打开失败写入原子标记文件。它达到三次后,才同时删除主库、-shm 和 -wal。

    SqliteValueBox.swift · L254–L303
  • #11-03非 UI非 UI 内核精选 11-3
    采样 CPU 时手工解 PAC 并限制栈遍历

    CpuProfiler 直接读取挂起线程的 arm64 寄存器和帧指针链。它先去掉 arm64e 返回地址的 PAC。它再用栈上下界、8 字节对齐和单调帧指针终止遍历。

    CpuProfiler.mm · L42–L99
  • #11-04非 UI非 UI 内核精选 11-4
    通知扩展选择崩溃,而不是带着半事务继续

    通知扩展在启动处理器时开启 setValueBoxStrictErrorHandling(true)。SQLite 的 BEGIN、COMMIT 或 step 失败会使扩展进程失败。系统随后展示未修改的推送。

    NotificationService.swift · L766–L778
  • #11-05非 UI非 UI 内核
    将硬链接的多个路径合并为一个缓存淘汰单元

    清理器用 (st_dev, st_ino) 识别相同物理文件。第一个路径记录大小。额外硬链接只附加到同一临时数据库行。淘汰时会一起取消链接全部路径。

    TimeBasedCleanup.swift · L255–L284
  • #11-06非 UI非 UI 内核
    用无行 ID 表和持久语句实现紧凑 ValueBox

    ValueBox 按逻辑表 ID 动态创建 t<ID>。二进制主键表可使用 WITHOUT ROWID。它按表缓存查询语句,并以 SQLITE_PREPARE_PERSISTENT 编译。

    SqliteValueBox.swift · L898–L952
  • #11-07非 UI非 UI 内核
    显式关闭 SQLite mmap,并分拆持久化取舍

    禁用缓存的连接把 SQLite 页缓存限制为 32。它显式关闭 SQLite mmap。它组合 WAL、synchronous=NORMAL 和内存临时表。检查点仍要求 fullfsync。

    SqliteValueBox.swift · L590–L628
  • #11-08非 UI非 UI 内核
    MTProxy 与 Web 代理只探测一个地址

    对 MTProxy 或 Web 代理,发现逻辑选择单个地址。它优先选择 IPv4。只要配置了代理,它也禁用 80 和 5222 的备用端口探测。

    MTDiscoverConnectionSignals.m · L194–L225
  • #11-09非 UI非 UI 内核精选 11-5
    用可取消的延迟递归实现指数退避

    每轮完成后才从计时器回调发起下一轮。间隔按两倍增长,并封顶。MTMetaDisposable 覆盖当前轮。外部取消会设置原子标记并处置当前订阅。

    MTDiscoverConnectionSignals.m · L228–L257
  • #11-10非 UI非 UI 内核
    到期时靠释放队列本地对象取消整个通知管线

    serviceExtensionTimeWillExpire() 不逐项停止网络任务。它将 QueueLocalObject 置空。处理器析构时会释放 MetaDisposable,从而处置轮询并断开保活连接。它再提交最新内容或原始内容。

    NotificationService.swift · L2748–L2765
  • #11-11非 UI非 UI 内核
    将名为 async 的队列调用改为同队列内联

    Queue 为串行队列设置 DispatchSpecificKey。在目标队列内,它让 async 和 sync 都直接执行闭包。主队列则用 Thread.isMainThread 识别。

    Queue.swift · L42–L81
  • #11-12非 UI非 UI 内核
    用启动时间加运行时长实现不可回拨的自动锁

    锁状态保存启动时间和开机运行秒数。若设备已重启,它立即锁定。未重启时,它使用 uptime 比较超时,不使用可被用户调整的墙上时间。

    AppLock.swift · L18–L39
  • #11-13非 UI非 UI 内核
    先累积消息索引,再批量更新未读状态

    每个 peer 的操作循环复用一个键、写缓冲和编码器。它将新增入站消息索引放进 Set。它只在遇到次序敏感操作或循环末尾时批量调用 addIncomingMessages。

    MessageHistoryTable.swift · L234–L260
  • #11-14非 UI非 UI 内核
    在新启动 VM 中提前挂载 Metal 工具链

    Tart 构建脚本在 Bazel 前串行挂载 Xcode 26 的 Metal cryptex。它检测 xcrun metal 失败后,用软链接覆盖 Xcode 默认工具链内的同名 stub。

    TartBuild.py · L968–L998
  • #11-15非 UI非 UI 内核
    从 Bazel 图生成 SwiftPM 的影子源码树

    脚本读取 Bazel 输出的模块清单。它在 spm-files 建立模块目录,但普通源码和 bazel-out 生成源码都只创建相对软链接。它拒绝不在声明模块目录中的输入。

    generate_spm.py · L333–L378
  • #12-01AppKitTGUIKit 核心精选 12-1
    用无键窗口自绘全功能 AppMenu

    它没有调用 NSMenu 的原生弹出流程。它为每层菜单创建透明、无键、非主窗口。它把窗口级别设为 .screenSaver。菜单内容由 MenuView 和表格绘制。

    AppMenuController.swift · L607–L681
  • #12-02AppKitTGUIKit 核心精选 12-2已发布 · 仍可用
    删除毛玻璃内部图层和滤镜

    VisualEffect 在每次 updateLayer() 后检查毛玻璃子树。它移除名称不为 backdrop 的内部子层。它只保留饱和度和高斯模糊滤镜。最后它清空所有内部层背景。

    VisualEffectView.swift · L47–L79
  • #12-03AppKitTGUIKit 核心精选 12-3
    用优先级重写 Window 键盘分发

    Window.sendEvent(_:) 先自动选择 responder。然后它先调度 .All 全局处理器。它只让优先级更高的全局处理器抢占具体按键。.supreme 可以跨过修饰键判定。处理器可消费、拒绝或交给 AppKit。

    Window.swift · L781–L824
  • #12-04AppKitTGUIKit 核心精选 12-4
    以条件 updateLayer 模拟 UIKit 变换属性

    View 把锚点、变换和子层变换包装为存储属性。属性变更时,它主动调用 updateLayer()。它只在至少一个属性被使用时请求 AppKit 的 updateLayer 路径。

    View.swift · L109–L153
  • #12-05AppKitTGUIKit 核心精选 12-5
    在基础 View 上统一翻转坐标并关闭隐式动画

    所有 View 默认 layer-backed。flip 默认值为 true。初始化关闭图层隐式动画,关闭自动子视图缩放,并按需重绘 layer。

    View.swift · L356–L397
  • #12-06AppKitTGUIKit 核心精选 12-6
    用空 CAAction 兼做层级进入通知

    SimpleShapeLayer 拦截 CA 的 onOrderIn 和 onOrderOut 动作查询。它把这两个动作转换为层级生命周期回调。无论事件是什么,它都返回无操作 CAAction。初始化时设置 Retina 比例。

    SimpleLayer.swift · L19–L42
  • #12-07AppKitTGUIKit 核心已发布 · 仍可用
    用未公开 KVC 键给 Layer 内容着色

    扩展用 KVC 读写 contentsMultiplyColor。它把该值包装为 layerTintColor。同一扩展还把大量 layer action 映射为 NSNull()。

    Extensions.swift · L560–L580
  • #12-08AppKitTGUIKit 核心
    让装饰 View 穿透命中测试

    isEventLess 为真时,视图仍参与显示。它不会作为命中结果返回。它还跳过同类、ImageView 和 LayerBackedView 的装饰层。

    View.swift · L421–L443
  • #12-09AppKitTGUIKit 核心
    用真实 hit-test 修正 hover 判断

    _mouseInside() 不只测试坐标是否在 bounds。它先拿内容视图的真实命中结果。它把 eventless 装饰层和 VisualEffect 视为可穿透。后续代码还沿 superview 链检查,并对 NSTableView 的行视图再做一次 hit-test。

    Extensions.swift · L724–L775
  • #12-10AppKitTGUIKit 核心
    用 NSTrackingArea 处理非激活窗口 hover

    控件在进入窗口或 superview 变更后重建 tracking area。它使用 .inVisibleRect,因此不依赖过期 bounds。它使用 .activeInActiveApp 和 .assumeInside。

    Control.swift · L220–L245
  • #12-11AppKitTGUIKit 核心已发布 · 仍可用
    将 CVDisplayLink 回调安全转送到主队列

    CVDisplayLink 的实时线程只向 DispatchSourceUserDataAdd 累加数据。主队列上的 source handler 再调用 Swift 闭包。C 回调通过 passUnretained 传递 source 指针。

    DisplayLinkAnimator.swift · L22–L64
  • #12-12AppKitTGUIKit 核心已发布 · 仍可用
    共享一个 DisplayLink 并按请求降帧

    SharedDisplayLinkDriver 统一维护请求。低帧率请求累积显示器真实周期。累计到目标周期 95% 时才回调。失效 link 在本轮后倒序清理。

    DisplayLinkAnimator.swift · L228–L280
  • #12-13AppKitTGUIKit 核心
    手工求解 CAMediaTimingFunction 的三次贝塞尔

    DisplayLinkAnimator 不把数值交给 CA。它从 CAMediaTimingFunction 提取控制点。它在每个显示刷新时求解贝塞尔曲线,再回调插值结果。

    DisplayLinkAnimator.swift · L448–L469
  • #12-14AppKitTGUIKit 核心
    导航 push 前冻结动态子内容

    push 前,导航控制器递归锁住新页面的动态内容。它把新页面从右侧放入。旧页面向左移三分之一宽度。它只为不透明页面添加边缘阴影。转场结束后会调用 restoreHierarchyDynamicContent()。

    NavigationViewController.swift · L770–L794
  • #12-15AppKitTGUIKit 核心
    把滚轮横向 delta 变成可取消的交互式导航

    横向滚轮手势会连续直接改两个页面的位置。当前页按完整 delta 移动。前页只移动三分之一。阴影、边界和导航栏也同步更新。返回值把已消费 delta 交给 Window 状态机。

    NavigationViewController.swift · L1225–L1249
  • #12-16AppKitTGUIKit 核心
    以 contentView 覆盖层替代 AppKit sheet

    Modal 是被加到 window.contentView 的 ModalBackground,不是 beginSheet。它注册最高层 responder、全局按键、Escape 和 Return。背景控件还负责外点关闭或把点击转交给下层。

    Modal.swift · L654–L700
  • #12-17AppKitTGUIKit 核心
    光栅化模态容器以压低阴影开销

    模态外容器允许阴影越界。内层容器裁剪圆角内容。外层开启 rasterization,并把比例设为当前屏幕比例。边框独立成一层。

    Modal.swift · L604–L631
  • #12-18AppKitTGUIKit 核心
    从触发控件位置缩放自定义 Popover

    Popover 先把控件中心转到屏幕坐标。它再转换到背景坐标作为缩放锚点。翻转背景时补偿 y。它提供缩放和 reveal 两种动画,完成后才设置 first responder。

    Popover.swift · L243–L274
  • #12-19AppKitTGUIKit 核心
    Tooltip 反转坐标并进行像素对齐

    Tooltip 从目标视图转换位置。父内容翻转时,它手动反转 y。x 坐标按 backing scale 向下对齐。它把提示限定在左右 10 pt 内。箭头可自动指向控件中心或鼠标位置。

    TooltipController.swift · L192–L231
  • #12-20AppKitTGUIKit 核心
    复制 SimpleLayer 实现桌布横向铺砖

    BackgroundView.TileControl 根据宽度计算图片块。它只增删不足的 SimpleLayer。每个块复用主层的内容、混合模式、背景和透明度。后续代码再更新每层 frame 并插入目标层。

    ViewController.swift · L94–L150
  • #12-21AppKitTGUIKit 核心
    以全局鼠标移动轮询保持 Popover 打开

    鼠标首次离开控件和 popover 后,代码启动 200 ms 的循环信号。循环检查鼠标是否回到任一方。未按下鼠标且没有上下文菜单时,它关闭 popover。

    Popover.swift · L282–L338
  • #13-01AppKit列表、滚动与文本精选 13-1已发布 · 仍可用
    用 CAEmitterLayer 私有行为做可交互隐形墨水

    代码动画 emitterBehaviors.randomAttractor0 的私有属性。它让粒子吸引力和位置持续随机变化。

    InvisibleInkDustView.swift · L340–L369
  • #13-02AppKit列表、滚动与文本精选 13-2
    用 Core Text 反查空偏移,修正跨行选区尾部

    如果选区结束索引的 Core Text 横向偏移为零,代码向前逐字符重试。它随后用修正后的两端偏移绘制高亮矩形。

    TextView.swift · L2482–L2499
  • #13-03AppKit列表、滚动与文本精选 13-3
    按稳定标识恢复滚动锚点,而不是恢复裸 offset

    更新前保存可见行的 stableId 和屏幕相对位置。更新后代码查找同一行,再重算 NSClipView 的 y 坐标。

    TableView.swift · L1882–L1914
  • #13-04AppKit列表、滚动与文本精选 13-4
    平滑滚动保存目标点,并可同步结算中途动画

    TGClipView 为每次动画滚动保存唯一目标。它在完成时按真实 bounds.origin 验证结果。

    TGClipView.swift · L158–L203
  • #13-05AppKit列表、滚动与文本精选 13-5
    把粘性标题移到滚动视图同级,并适配翻转列表

    代码在可见行中按坐标方向寻找最近的 TableStickItem。TableView 随后把 TableStickView 放在滚动内容之外。

    TableView.swift · L1424–L1445
  • #13-06AppKit列表、滚动与文本精选 13-6
    粘性标题压住下一标题时,手算 y 轴推挤和层级

    代码以候选标题的实际 row rect 计算覆盖标题的 y 偏移。它为第二个粘性标题预留 secondaryOffset。

    TableView.swift · L1503–L1553
  • #13-07AppKit列表、滚动与文本精选 13-7
    用 presentation layer 接住行位移,再人工补 Core Animation

    列表插入时读取 presentation() 层的当前位置。它从屏幕上的实际位置继续动画,而不是从 model layer 跳变后的值开始。

    TableAnimationInterface.swift · L95–L110
  • #13-08AppKit列表、滚动与文本
    为倒序消息流写独立的增量 diff

    mergeListsStableWithUpdatesReversed 以反向比较规则生成删除、插入和更新索引。它还保留更新项的旧索引。

    MergeLists.swift · L288–L312
  • #13-09AppKit列表、滚动与文本
    只重排可见行,并用手工行高通知驱动 NSTableView

    窗口改宽时,代码只对可见 range 调用 makeSize。只有高度变化的行才 reload 并通知 NSTableView 重算高度。

    TableView.swift · L1083–L1101
  • #13-10AppKit列表、滚动与文本
    用 TextNode 复用相同输入的 Core Text layout

    layoutText 把约束尺寸、截断策略、镂空、选中态和 attributed string 都作为缓存键。全部相等时直接复用旧 layout。

    TextNode.swift · L342–L369
  • #13-11AppKit列表、滚动与文本
    绕过 NSTextView,直接逐 glyph run 绘制消息文本

    TextNode 自己翻转 Core Graphics 坐标系。它逐行、逐 CTRun 绘制文本,并计算居中 pen offset。

    TextNode.swift · L305–L335
  • #13-12AppKit列表、滚动与文本
    将内嵌内容留在文本流中,再交给独立图层渲染

    代码从 CTLine 精确得到内嵌范围的左右边界。它使用 floor/ceil 和备用偏移,减少非整数 glyph 坐标的缝隙。

    TextView.swift · L1014–L1049
  • #13-13AppKit列表、滚动与文本
    spoiler 遮罩时同步隐藏浮在文字上的嵌入层

    代码以嵌入层中心点和 spoiler rect 相交为条件,把对应 layer 的 opacity 置零。

    TextView.swift · L3383–L3397
  • #13-14AppKit列表、滚动与文本
    输入框用第二套 TextKit 栈测量高度,避免污染显示布局

    InputTextView 用独立的 storage、layout manager 和无限高 container 测量高度。它仅在文本或宽度改变时使测量布局失效。

    ChatInputTextView.swift · L695–L721
  • #13-15AppKit列表、滚动与文本
    重写 NSTextContainer 的行片段,为引用块预留不规则边距

    输入框在 TextKit 请求每个 line fragment 时检查 quote 属性。引用块行会被动态缩窄并右移。

    ChatInputTextView.swift · L467–L503
  • #13-16AppKit列表、滚动与文本
    自定义表情不是 NSTextAttachment,而是 glyph rect 上的叠加层

    代码用 customEmoji 属性找字符范围,再转换为 glyph range 和 bounding rect。外层的 emojiContent 接收这些 rect 并渲染表情。

    ChatInputTextView.swift · L743–L769
  • #13-17AppKit列表、滚动与文本
    发送键先检查 hasMarkedText(),避免截断中文等输入法候选

    Enter 只有在没有 marked text 时才进入发送委托。输入法组合文本存在时,事件继续交给 NSTextView。

    ChatInputTextView.swift · L1163–L1184
  • #13-18AppKit列表、滚动与文本
    为输入控件单独持久化拼写和语法检查开关

    InputTextView 覆写 AppKit 拼写和语法开关。它以 TGGrowingTextView 后缀写入 UserDefaults。

    ChatInputTextView.swift · L1292–L1309
  • #13-19AppKit列表、滚动与文本
    横向滚动直接把纵向滚轮 delta 映射到 x,并在边界冒泡

    横向容器把触控板的垂直 delta 转为 x 方向。它把 x 对齐到屏幕像素,并在无可滚动内容时交给父视图。

    HorizontalTableView.swift · L124–L150
  • #13-20AppKit列表、滚动与文本
    数字计数器把字符串拆成多个 TextView,分别滚入和滚出

    DynamicCounterTextView 不直接替换整段文本。它让离场和入场的 token 各自沿 y 轴移动,并叠加透明度动画。

    DynamicCounterTextView.swift · L171–L201
  • #13-21AppKit列表、滚动与文本
    强制关闭自动行高和默认背景,完全接管 NSTableView 的行几何

    TGFlipableTableView 关闭交替背景、自动行高和图层重绘策略。外层 TableView 负责尺寸、焦点和辅助元素。

    TableView.swift · L510–L541
  • #13-22AppKit列表、滚动与文本
    滚动列表更新时按可视范围筛选动画对象

    TableAnimationInterface 先把插入和删除限制在可见行范围。它再以这些行的高度决定是否需要补偿 content offset。

    TableAnimationInterface.swift · L41–L79
  • #14-01AppKit私有系统集成已发布 · 仍可用
    拼接私有类名,并用函数指针调用 CAEmitterBehavior

    代码运行时构造私有类 CAEmitterBehavior。代码也构造真实 selector behaviorWithType:。代码把 method(for:) 的 IMP 强转为 C 函数指针。随后,代码通过 emitterBehaviors 的 KVC 键装配吸附和生命周期行为。

    InvisibleInkDustView.swift · L12–L18
  • #14-02AppKit私有系统集成精选 14-1已发布 · 仍可用
    用监听型 CGEvent tap 实现全局按住说话

    代码监听 HID 级键盘、修饰键和其他鼠标键。它把 CGEvent 转回 NSEvent。它选择 .listenOnly,所以监听器不拦截原始输入。权限不足时,代码退化为应用内 NSEvent monitor。

    PushToTalk.swift · L119–L169
  • #14-03AppKit私有系统集成精选 14-2已发布 · 仍可用
    用 FinderInfo 位和资源叉永久改写 Dock 图标

    代码先让 sips -i 生成图标资源叉。代码把资源叉复制到主 .app 包内名为 Icon\r 的文件。代码设置 com.apple.FinderInfo 第 8 字节的第 2 位。最后,代码删除 Dock 图标缓存,并执行 killall Dock。

    Dock.swift · L64–L93
  • #14-04AppKit私有系统集成精选 14-3版本差异
    深挖 AVMovableView,再点击隐藏的全屏按钮

    代码按类名找到 AVKit 内部的 AVMovableView。代码假设六层子视图链的末端是 NSButton。代码通过 performClick 触发那个按钮的全屏动作。

    MGalleryExternalVideoItem.swift · L257–L267
  • #14-05AppKit私有系统集成精选 14-4版本差异
    重排 NSTitlebarView 内部子视图,腾出 50 pt 标题栏

    代码改写 window.titleView 的 frame。代码递归按字符串找到私有 NSTitlebarView。代码遍历前三个内部子视图。代码隐藏它们,并按 Retina 像素取整后重设坐标。

    PassportWindowController.swift · L89–L104
  • #14-06AppKit私有系统集成精选 14-5系统结构已变化
    用私有文本菜单 selector 删除系统子菜单

    代码检查 AppKit 生成的文本右键菜单。代码通过 _shareServiceSelected: 识别系统分享服务项。代码发现分享、字体或替换项时,删除整个父菜单。代码再插入 Telegram 的文本转换项。

    ChatInputTextView.swift · L987–L1013
  • #14-07AppKit私有系统集成精选 14-6已发布 · 仍可用
    直接调用未公开的对角缩放光标 selector

    代码向 NSCursor 类发送两个下划线 selector。它取得两种对角窗口缩放光标。调用点把它们用于图像编辑窗口和通话窗口的角落拖拽。

    ObjcUtils.m · L30–L43
  • #14-08AppKit私有系统集成精选 14-7系统结构已变化
    按私有 MKZoomSegmentedControl 类名移动地图缩放控件

    代码在每次 layout 后扫描 MKMapView 的内部树。代码找到 MKZoomSegmentedControl。代码把它固定到 (20, 20)。代码在调整 map frame 时暂时置空 delegate。

    LocationModalController.swift · L231–L246
  • #14-09AppKit私有系统集成
    用 CATransaction 包裹系统全屏切换,并延迟发布状态

    Window 在调用 super.toggleFullScreen 前后显式开关 Core Animation 事务。它允许 processFullScreen 先介入。它把保存状态写在实际切换后。无介入器时,它下一轮主队列才发布信号。

    Window.swift · L936–L958
  • #14-10AppKit私有系统集成已发布 · 仍可用
    混用 IOPS 摘要和 AppleSmartBattery 注册表私有键

    代码先用公开 IOPS 获取充满或耗尽剩余时间。代码再连接 IOKit 的 AppleSmartBattery 服务。代码直接读取容量、循环次数、温度和设计循环数等键。它把电流与电压换算为带正负号的瓦数。

    InternalFinder.swift · L32–L53
  • #14-11AppKit私有系统集成已发布 · 仍可用
    用 Carbon 键盘布局翻译物理键,并重试非 Unicode 布局

    代码先映射功能键和修饰键。普通物理键则从当前 ASCII 键盘布局取得 UCKeyboardLayout。代码把 Cocoa 修饰键转成 Carbon 标志。代码用 UCKeyTranslate 得到用户当前布局下的快捷键文案。

    HotKeyUtilities.m · L89–L129
  • #14-12AppKit私有系统集成
    把可能阻塞的输入监控授权移出主线程

    无提示检查同步返回授权状态。需要提示时,代码在用户发起队列调用 IOHIDRequestAccess。代码立即返回 NO。10.15 以前,它回退到辅助功能授权。另一方法还拼接系统设置深链。

    HotKeyUtilities.m · L150–L176
  • #14-13AppKit私有系统集成
    直接用 POSIX 扩展属性存取 Finder 元数据

    代码先以空缓冲区查询属性长度。代码再分配精确长度的缓冲区并读取原始字节。它把 POSIX errno 包装成 Swift 错误。Dock 包用它读写 com.apple.FinderInfo 和 com.apple.ResourceFork。

    XAttr.swift · L4–L29
  • #14-14AppKit私有系统集成已发布 · 仍可用
    从 IOHIDSystem 读取原始空闲纳秒数

    代码遍历 IOHIDSystem。代码读取注册表键 HIDIdleTime。代码把读出的数右移 30 位。通知锁定逻辑用该值与超时比较。在线状态还要求该值小于 30 秒。

    ObjcUtils.m · L1235–L1257
  • #14-15AppKit私有系统集成已发布 · 仍可用
    为 M2 后的泛化 hw.model 启动 system_profiler

    代码不只读 sysctlbyname("hw.model")。它启动 Apple 的 system_profiler。代码请求 JSON 硬件摘要。代码从 machine_name 和 chip_type 合成展示用型号。外部命令失败后才回退到 sysctl。

    TelegramSystem.swift · L4–L52
  • #14-16AppKit私有系统集成
    用双时间戳文件做无信号处理器的崩溃哨兵

    启动时,代码检查名为 crashhandler 的文件。代码每次中间保存更新时间戳。下次启动时,文件只含一个时间戳,或两个时间戳相差小于 10 秒,都会被视为异常退出。应用代理启动后立即写入一次,并在终止时删除文件。

    CrashHandler.swift · L11–L50
  • #14-17AppKit私有系统集成
    对 Spotlight 做稳定 ID 差分,而非全量重建索引

    每个 Spotlight 条目 ID 带帐户 ID 和 peer ID。代码维护前一份条目列表。代码计算删除、插入和更新集合。它只把插入和更新交给 indexSearchableItems。它只删除消失的标识符。应用收到 CSSearchableItemActionType 后,再按帐户路由…

    Spotlight.swift · L104–L157
  • #14-18AppKit私有系统集成
    把 Dock 徽标字符串当成状态栏图标的输入

    未读数先写入 NSDockTile.badgeLabel。状态栏图标更新函数随后再读取这个字符串。代码在资源队列生成图像。代码回主队列替换状态栏图片。它也按 macOS 版本和外观选择黑白颜色。

    GlobalBadgeNode.swift · L199–L201
  • #15-01AppKit渲染与应用层精选 15-1已发布 · 仍可用
    用 XOR 帧差分再用 LZFSE 压缩贴纸像素缓存

    它把连续帧按 64 位字进行 XOR。它再把差分结果写为 LZFSE 压缩帧。读取时,它解压后再次 XOR 前帧,得到原始 BGRA 帧。

    LottieBufferCompressor.swift · L308–L350
  • #15-02AppKit渲染与应用层精选 15-2
    让相同内联贴纸共享一个解码和播放上下文

    MultiTargetContextCache 用动画键和唯一值索引上下文。第二个同键图层只注册回调。最后一个订阅者移除时,它才销毁上下文。

    InlineStickerItemLayer.swift · L208–L250
  • #15-03AppKit渲染与应用层精选 15-3
    按运行循环把多个贴纸绘制合入一个 Metal 命令缓冲

    同一 LottieRunLoop 的待绘制项进入 list。定时器每帧只创建一个 MTLCommandBuffer。各项目编码后统一 commit 和 present。

    LottiePlayer.swift · L1548–L1584
  • #15-04AppKit渲染与应用层精选 15-4
    GIF 用 AVSampleBufferDisplayLayer 自行供帧,而非逐帧 NSImage 动画

    它让 AVAssetReader 只在显示层可接收时取样并 enqueue。它关闭样本复制。它把显示层控制时钟绑定到主机时钟,并在读完后重建 reader。

    GIFPlayerView.swift · L245–L301
  • #15-05AppKit渲染与应用层精选 15-5
    通话帧池达到五个缓冲就丢帧,优先控制实时延迟

    它按分辨率复用 CVPixelBufferPool。池占用超过五张时,它立即丢弃新通话帧。它随后把 I420 拷入 NV12 缓冲。

    MetalVideoRenderView.swift · L286–L334
  • #15-06AppKit渲染与应用层精选 15-6
    通话背景先统一颜色格式,再降采样做两次计算模糊

    它先把 BGRA、双平面 YUV 或三平面 YUV 统一转成 RGBA。仅当背景层可见时,它降采样并依次执行横向和纵向计算模糊。

    PrivateCallVideoLayer.swift · L190–L257
  • #15-07AppKit渲染与应用层
    Thanos 效果把视图快照变成 GPU 像素粒子,并只在 M2 以上启用

    它截取目标视图的像素快照。它把快照和转换后的坐标交给覆盖整个窗口的 DustLayer。粒子结束后,该无交互覆盖视图自动移除。

    DustLayer.swift · L369–L418
  • #15-08AppKit渲染与应用层精选 15-7
    Thanos 粒子只在有项目且可见时订阅全局显示链接

    层仅在有粒子且已进入层级时订阅共享显示链接。每个项目只初始化一次粒子状态。每帧计算着色器更新状态,再按 particleCount 实例化绘制。

    DustLayer.swift · L170–L229
  • #15-09AppKit渲染与应用层
    CPU 模糊把三个颜色通道塞入 64 位累积值,并用滑动二阶差分

    实现用固定半径 7 做横向和纵向两遍模糊。它把 RGB 通道打包在 uint64_t。它通过二阶差分递推滑动窗口,并以首尾像素延拓边缘。

    FastBlur.m · L120–L215
  • #15-10AppKit渲染与应用层
    软件渐变先渲染很小的位图,再动画 contents

    代码按 16×16 量级的适配尺寸生成软件渐变帧。它在 CPU 插值颜色锚点。它把预制 CGImage 列表交给 Core Animation 的 contents 关键帧动画。

    SoftwareGradientBackground.swift · L287–L372
  • #15-11AppKit渲染与应用层
    Shimmer 将压缩路径缩略图解码为异步位图掩码

    它把自定义紧凑字节编码还原成路径字符串。它在并发队列将路径栅格化为 CGImage。主线程只把结果设给 mask 或前景层。

    ShimmerLayer.swift · L158–L175
  • #15-12AppKit渲染与应用层
    声音波纹把原始音量做指数平滑,再生成闭合贝塞尔曲线

    视图将输入音量归一化,并给三层 Blob 同时调速。显示链接用 0.9/0.1 的指数低通生成展示音量。路径以相邻点导出的控制点闭合为贝塞尔曲线。

    VoiceBlobView.swift · L78–L112
  • #15-13AppKit渲染与应用层
    聊天贴纸按尺寸降低帧率,并用缩略图无闪接力

    它用映射读取载入完整动画数据。小于 200 pt 的非表情贴纸限定 30 fps。缩略图一直显示到播放器真正进入 .playing,再切换图层可见性。

    ChatMediaAnimatedStickerView.swift · L188–L246
  • #15-14AppKit渲染与应用层
    内联贴纸延迟冷启动,却让缓存命中立即开始

    新上下文冷启动时等待 100 ms。命中共享缓存、强制播放或指定无延迟时立即启动。回调发现图层离开窗口后会异步撤销自身。

    InlineStickerItemLayer.swift · L576–L627
  • #15-15AppKit渲染与应用层
    内联贴纸仅在窗口活跃且滚动区域可见时播放

    播放条件同时要求可播放、窗口活跃和 visibleRect 非零。它监听窗口焦点、滚动裁剪区边界和文档视图 frame 变化,立即重新计算条件。

    InlineStickerItemLayer.swift · L280–L322
  • #15-16AppKit渲染与应用层
    PiP 不依赖系统 PiP 控制器,而用跨 Space 的非激活面板模拟

    它用 NSPanel 自建浮窗。面板不抢键盘焦点,并加入所有 Space 和全屏辅助窗口集合。它保留一个移到 (3000, 3000) 的 Window,以让 PictureInpictureView.window 返回该窗口。

    PIPVideoWindow.swift · L69–L150
  • #15-17AppKit渲染与应用层
    GIF 转码器修正极短延时,并直接绘入 CVPixelBuffer

    它优先使用 GIF 的未钳制延时。低于 11 ms 的延时统一修正为 100 ms。它从像素池取 ARGB 缓冲,并让 Core Graphics 直接写入该缓冲。

    TGGifConverter.m · L185–L255
  • #15-18AppKit渲染与应用层已发布 · 仍可用
    软件视频预览锁定 CVPixelBuffer 后用 vImage 一次缩放

    预览路径直接锁定解码帧的像素内存。它用 vImageScale_ARGB8888 缩放到目标 backing scale。最后才包装为 CGImage。

    SoftwareVideoSource.swift · L26–L50