← 所有文章

iOS 27 中的 SwiftUI 性能与互操作

LazyVStack 并不知道自己有多高。它根据已经放置的视图的平均尺寸以及预计还剩多少视图来估算自身高度,然后在滚动过程中实时修正这个估算值。1 来自 UI Frameworks 团队的三场 WWDC26 讲座,以这一条事实(以及它在图形和互操作领域的对应版本)为起点,构建出一套关于 SwiftUI 在 iOS 27 重负载下行为表现的可用模型:滚动如何保持流畅、GPU 效果如何组合,以及 SwiftUI 如何嵌入到一款您已经发布的 AppKit 或 UIKit 应用中。

这三场讲座读起来就像是同一个论点的三种表达方式。当您不再与惰性栈的估算机制对抗时,它的性能就会表现良好;当您把每个修饰符都视为流水线中的一个环节时,着色器效果就能顺利组合;当您让 @Observable 和各种 representable 协议来承载衔接缝时,互操作就能正常运作。这三者都不是功能发布公告。每一场讲座都把某个机制讲得足够透彻,让您能够预测框架的行为,而不是去猜。

TL;DR / 要点速览

  • LazyVStack 只会评估填满可见矩形区域的那些视图;屏幕外的高度以及内容偏移量都是估算出来的,因此绝对内容偏移量的读数并不稳定,您应当优先使用 .onScrollTargetVisibilityChange 这类相对可见性 API。12
  • 预取会把”显示一个视图”这件工作拆散,在视图出现之前分摊到多个帧上完成;请在视图的初始化器中(而非 onAppear 中)完成视图设置,这样预取好的工作就不会被丢弃。1
  • 避免在 ForEach 叶子节点中出现数量动态变化的子视图:在 body 中用条件语句做过滤会让视图按索引保持存活,因此应当在数据层面做过滤(在 Query 上使用 Predicate)。1
  • SwiftUI 暴露了三个着色器入口点(colorEffectdistortionEffectlayerEffect),能力依次递增;只有 layerEffect 能采样相邻像素,而这正是模糊和域扭曲所需要的。3456
  • 着色器是无状态的,因此动画来自于将 TimelineView 的时间戳作为参数传入;帧与帧之间没有任何东西被保留下来。37
  • AppKit 和 UIKit 通过 @Observable 获得自动重绘(不再需要手动调用 needsDisplay),而 SwiftUI 则通过 NSHostingViewNSGestureRecognizerRepresentableNSHostingMenuNSHostingSceneRepresentation 嵌入到现有应用中。8910

惰性栈靠估算运行

Watch on Apple Developer ↗
UI Frameworks 工程师 Rens 解释说,LazyVStack 自上而下布局,一旦可见矩形区域被填满就停下来,剩下的部分则进行估算。

关于惰性栈,首先要内化的一点是:它有意地用正确性换取效率。与 VStack 不同,LazyVStack 不会评估或渲染不可见的视图;它自上而下地布局视图,一旦可见矩形区域被填满就停止,随着视图滚入而添加它们,随着视图滚出而移除它们。1 收益显而易见。代价却很微妙:由于栈从不加载所有视图,屏幕外视图的高度是根据此前视图的平均值估算出来的,理想宽度会坍缩成第一个子视图的宽度,而可见区域上方的空间本身也只是个近似值。1

这种估算并不是您绕过一次就完事的 bug;它是其他每一个惰性栈决策所依托的基底。讲座 321 用一次方向切换把这个后果讲得很具体。旋转一部 iPhone,最顶部的可见视图会保持锚定,但栈尚未测量上方那些视图的确切新布局。滚回顶部时,栈必须进行调和:它修正可见区域上方的估算空间,并将滚动视图的内容偏移量按相同的量更新,从而让顶部的内容偏移量精确落在零。1 惰性栈与包裹它的滚动视图精确地协调位置和偏移量,使得随着估算值的更新,可见子视图的相对位置永远不会跳动。1

由此推出的可操作结论,是一条关于该信任哪些滚动 API 的规则。由于绝对内容偏移量是估算出来的,读取它(比如用 .onScrollGeometryChange 在滚动超过 100 点后隐藏一个按钮)会得到一个随估算值逐渐稳定而漂移的阈值。2 稳定的信号是相对可见性。.onScrollTargetVisibilityChange 修饰符会在滚动视图中可见子视图的集合发生变化时触发,因此一个”滚动以展示”的按钮可以将其可见性绑定到屏幕上显示了哪些行,并使用一个阈值(讲座用的是 80%),而不是一个不稳定的像素计数。21 同样的逻辑也判了 .scrollTransition 的”罪”:一个把视图推出其原始框架的变换,可能会让栈误以为某个可见视图已经移到屏幕外而过早地丢弃它,所以任何滚动过渡都必须防止那些本不该可见的视图被推可见矩形区域。111

为什么您的视图结构体并不是那些子视图

惰性栈加载的子视图,并不与您所编写的视图结构体一一对应。一个由 StepView 组成的 ForEach 会为每一步解析出一个 StepView,但如果每个 StepView 的 body 返回两个顶级视图(一张图示和一段说明)而没有外层布局包裹,栈就会把它们各自分别加载。1 对栈来说重要的那个数字,是解析后的子视图数量,而不是结构体的数量。

陷阱在于动态变化的子视图数量。如果一个 StepView 根据某个环境值而返回一个子视图或零个子视图,栈就再也无法信任索引了,因为前面视图的数量可能会改变。于是它会以防万一地把前面那些 StepView 实例保持存活,这意味着一个毫不相关的环境变化都可能触发已滚出屏幕的视图的 body 评估,而栈不会释放它们的状态。1 修复方法是把过滤从视图上移到数据上:如果您使用 SwiftData,就把条件放进 QueryPredicate 里,这样无需构造任何视图就能知道子视图数量。1 在 body 中解包一个可选值会产生同样的”存活更久”的效果;更干净的做法是在更上层显示一个 ContentUnavailableView,而不是让惰性栈持有部分解析的行。1

预取是让估算感觉起来很快的机制。在滚动过程中,滚动视图只有到帧截止时刻为止的时间来更新偏移量、渲染视图并运行您的偏移变化处理逻辑;如果显示一个新视图超出了这个预算,就会丢帧,您就会看到卡顿。1 为了防止这种情况,惰性栈会检查是否有空余时间,如果有,就提前完成一个即将出现的视图的部分工作(评估它的 body 和布局,甚至把一个嵌套的 LazyHStack 分摊到多个帧上),这样当视图出现时,大部分工作已经完成。1 这就是为什么这场讲座对 onAppear 态度鲜明:如果您在 onAppear 中设置一个视图,您就丢弃了预取好的工作,迫使它在出现时重做一遍,有时还会拉入比所需更多的视图,从而拖累滚动。请在视图的初始化器中完成设置,让它在到达时就处于合理状态,而把 onAppear 留给真正与”出现”相关的工作,比如在无限滚动中获取下一页。1 在反向滚动时,body 甚至可能在预取期间运行,而 onAppear 完全不会触发。1

高级图形不过是一条流水线

Watch on Apple Developer ↗
UI Frameworks 工程师 Haotian 把高级效果框定为一根根标准管道连接在一起:”高级”在于构建方式,而非复杂程度。

讲座 322 把”高级图形”重新框定为组合。每个 SwiftUI 修饰符都是一根管道,它接收数据、对其进行变换,再把数据传递下去;高级的结果就在于您如何连接这些管道,而不在于任何单个复杂的 API。3 这场讲座通过串联一系列普通环节,构建出一个 Apple Music 风格的实时歌词视图:把封面图模糊处理使其后退,在它上面运行一个着色器,用时间来驱动这个着色器,再把歌词文本的滚动同步到同一个时间源。3

着色器这一环节才是真正需要做选择的地方。SwiftUI 通过三个能力逐级攀升的效果入口点来调用 Metal 着色器函数。colorEffect 根据每个像素的位置和原始颜色来变换它的颜色,这足以应对诸如灰度转换之类的需求。4 distortionEffect 则是把一个位置映射到另一个位置(您告诉 SwiftUI 从那个位置去采样这个位置的颜色),它处理的是不涉及颜色的几何扭曲。5 layerEffect 最为灵活:它把整个视图的图层交给着色器,因此输出像素可以采样它的相邻像素或整个区域,而这恰恰是模糊和更丰富的扭曲所需要的。63

讲座中的域扭曲背景就使用了 layerEffect。一个统一的 float2 偏移量会把每个像素都平移相同的量,那只会让图像滑动;有机的运动需要逐像素的变化,因此着色器会采样一个预先计算好的 NoiseTexture(作为图像传入,在 Metal 那一侧以 texture2d 的形式到达),它的红色和绿色通道在每个 UV 坐标处提供不同的 X 和 Y 偏移量。3 采样噪声一次会让图像扭曲;采样两次,第二次是在第一次采样所偏移到的位置上采样,则会产生流动的团块。这种二阶技巧就是域扭曲,讲座还指出了它配套的可下载示例 app,其中带有可实时预览参数的功能。3

有两条框架层面的事实让这个动画得以成立。着色器是无状态的:它们不保留上一帧的任何记忆,输出只取决于您传入的参数。3 因此运动不能来自着色器内部;它必须被喂进去,而 TimelineView 就是供给它的那根管道,它按动画时间表在每一帧触发,并附带一个时间戳。73 把那个时间戳传入着色器,将它加到噪声采样位置上,图案就会流动起来。歌词文本那一侧则从另一个方向复用了同一个时间源:播放时间戳挑出当前那一行(加粗且清晰,其余的则淡化),而一个 onChange 让那一行随时间推进而保持居中。123 当前行上那个浮动的时间戳,其定位不是用 offset(那会需要两个视图的尺寸),而是用一个对齐参考线的覆盖来实现,它从语义上重新定义了某个对齐方式的对齐点,使得子视图的顶边附着到其容器的底边上,而无需手动设置偏移。133

SwiftUI 嵌入 AppKit 或 UIKit 应用

Watch on Apple Developer ↗
UI Frameworks 工程师 David Nadoba 指出,SwiftUI 从一开始就被设计为能与 AppKit 和 UIKit 并肩工作,正如 Swift 当初被设计为能与 Objective-C 协作一样。

这场互操作讲座以一个重新框定整个采用问题的观点开场:大多数应用其实已经在隐式地使用 SwiftUI 了。在新的设计中,NSSliderNSSwitchNSSegmentedControl 这些 AppKit 控件底层都是用 SwiftUI 渲染的,而 Liquid Glass 也通过 SwiftUI 在各框架之间共享了其实现的大部分内容。8 所以”采用 SwiftUI”与其说是一次重写,不如说是一个决定——决定在哪里把衔接缝显式地做出来。

第一步根本不需要任何 SwiftUI。AppKit 和 UIKit 现在会自动观察 @Observable 类型:把一个模型类标记为 @Observable,在像 drawKnob 这样的绘制方法内部读取它的属性,AppKit 就会追踪每一次访问,并在任何被访问的属性发生变化时重绘,从而淘汰掉以往每当一个滑块的值影响另一个滑块外观时您必须写的那句手动 needsDisplay = true814 观察机制不止覆盖 draw(_:),还延伸到 updateConstraints()layout()updateLayer() 以及对应的 NSViewController 方法,而 UIKit 触及的范围更广,深入到 UIButtonUICollectionViewCell 等等。8 它在 2026 年的各版本中默认开启,并可通过 Info.plist 向后部署到 macOS 15(NSObservationTrackingEnabled)和 iOS 18(UIObservationTrackingEnabled)。8

一旦模型变成了 @Observable,真正的 SwiftUI 衔接缝就很小了。讲座把一个基于滑块的颜色选择器重建为一个用 Canvas 绘制的圆形 SwiftUI 控件(Canvas 是一个类似于 drawRect 的即时模式 API,配合 withCGContext 可复用现有的 Core Graphics 代码),并复用了同一个 @Observable ColorModel158 要把它嵌入到 AppKit 期望出现一个视图的地方,就用 NSHostingViewNSView 的一个子类)把它包起来;由于模型已经在驱动更新,所需要做的就只有这层包裹。168 现有的手势代码无需重写即可沿用:一个 ForceClickGestureRecognizer 通过 NSGestureRecognizerRepresentable(实现 makeNSGestureRecognizerhandleNSGestureRecognizerAction)触达一个 SwiftUI 视图,然后用普通的 .gesture 修饰符附加上去,并与 SwiftUI 自身的拖拽手势共存。178 同一个 representable 家族还包括用于反方向嵌入 NSViewNSViewRepresentable8

这条衔接缝可以向上扩展到菜单和场景。一个持有 ButtonPicker 的 SwiftUI View,通过 NSHostingMenuNSMenu 的一个子类)变成一个真正的菜单,被设为添加到主菜单中的某个 NSMenuItem 的子菜单,再配合 keyboardShortcut 为该动作提供一条非手势的输入路径,以服务那些无法用力按压的输入设备。188 整个 SwiftUI 场景也能附加进来:一个 MenuBarExtra 通过 NSHostingSceneRepresentation 触达一款现有应用,在 applicationWillFinishLaunching 中经由 addSceneRepresentation 添加,再用一个 Settings 场景里的 Toggle 来控制是否插入这个额外项,并用 openSettings() 环境动作从一个 @IBAction 中打开设置。198 这场讲座收尾的那个观点才是关键所在:它所涵盖的每一个 API 都已随 2026 年的各版本或更早版本一同发布,而且并不要求一款应用必须完全用 SwiftUI 才能从中受益。8 不过在这个周期的其他地方,现代化的推进有着更硬的一面:iOS 27 把 UIKit 基于场景的生命周期设为了启动的硬性要求,因此一款您用最新 SDK 重新构建的应用,如果从未采用过场景,就会无法启动。

SwiftUI 团队在实验室里补充了什么

来自 WWDC26 UI Frameworks 团队实验室(group lab)的两点澄清,让这些讲座所描述的失效(invalidation)模型变得更加清晰。两者都是根据本地转录的录音转述的;苹果并未为这些实验室发布官方字幕——这与贯穿 Swift 团队自己的 Group Lab 的那道字幕缺口如出一辙,在那场实验室上,语言工程师们回答了关于并发与路线图的问题。

把代码从 body 移到一个计算属性里,并不会带来任何失效方面的好处。SwiftUI 仍然会在每次重新运行 body 时重新运行那个属性,所以这次移动只是提升了可读性,仅此而已。20 性能边界出现在上一层:抽取出一个单独的视图类型,SwiftUI 就能独立地使它失效,在它的输入发生变化时只重新运行那一个视图,而不是整个外层 body。当您想让 SwiftUI 少做工作时,请去找一个新的视图,而不是一个新的属性。

每一次环境变化都会让所有读取该环境值的视图失效。读取环境是廉价的;环境的频繁变动则不然。21 请把变化频繁的值排除在环境之外(讲座举的例子是当前时间),因为一个每帧都更新的值,会在它每次变动时把每一个读取者都拖入重新评估。请把易变的值沿着真正需要它的那条路径往下传,而让环境去承载那些保持不变的东西。

后续的一场实验室讲座又补充了三个机制细节,全部根据 WWDC 2026 SwiftUI Group Lab 的本地转录录音转述,苹果未为这些实验室发布官方字幕。

部分图计算(partial graph evaluation)解释了预取好的工作究竟去了哪里。专家组描述了一个惰性栈,它在当前帧渲染完毕后剩余的帧时间里评估即将出现的单元格的 body,然后在下一帧开始前恰好停下来。22 他们标出的隐患在于:一个会重新配置单元格、使其因尺寸改变而必须重新布局的 onAppear,会把那份预取好的工作丢弃掉。他们的建议是在单元格的初始化器中完成尺寸相关的工作,而不是在 bodyonAppear 中,这样预取才能存活下来。22

那套互操作的心智模型,来自一位在 UIKit 上有十余年经验的专家组成员。UIKit 自上而下布局,从窗口向内一直到叶子节点,而 SwiftUI 则自下而上构建,从最内层的节点向外。22 当两者交错时,专家组把这种交替分层的排布称作三明治或蛋糕,这也是为什么深度交错会变得微妙:每个框架都想从相反的那一端来驱动布局过程。22

上文那个关于环境的观点得到了一个生动的真实例子。当被问到他们见过的最严重的环境误用时,专家组举了把滚动位置放进环境里的例子,它在滚动时每帧都会更新,于是每帧都让每一个读取者失效。22

第二场 SwiftUI Group Lab 讲座又补充了四个机制细节,全部根据 WWDC 2026 SwiftUI Group Lab(第二场)的本地转录录音转述;苹果未为这些实验室发布官方字幕。

onGeometryChange(for:of:action:) 修饰符之所以比它听起来更好用,原因在于它的两个闭包是如何分工的。变换闭包每帧都带着实时几何信息运行,但只有它返回的那个值才决定动作是否触发,因为结果类型是 Equatable 的,动作只有在那个值发生变化时才会运行。23 所以返回一个粗粒度的值(一个尺寸分档或一个布局断点,而不是原始尺寸),就能把一个帧率级别的信号转换成只在阈值处触发两次、而非持续触发的信号。专家组还为此搭配了一句警告:GeometryReader 对它所包裹的子视图而言开销很大,应当把它限制在 background 中,让它在测量的同时不去驱动主布局。23

一个设计良好的动态属性可以彻底取代大部分 onChange 的工作。DynamicPropertyupdate() 方法会在视图的 body 之前紧接着运行,所以一个自定义的属性包装器可以在那个时刻同步地提供一个已经缓存好的值(专家组举的例子是一张图像),从而跳过 onAppear 的往返以及它所触发的重新渲染。24 专家组的说法是,onChange 的大多数用法都可以被一个构建良好的动态属性所取代。24

一个在 ScrollView 内部绘制超出其上报布局边界之外的视图,可能会被剔除(culled),因为系统是根据它被告知的边界来判定它已移出屏幕,而不是根据它实际绘制的位置。25 专家组把这一点点名为自定义下拉菜单和遮罩层溢出其宿主视图、然后在滚动途中消失这一具体故障背后的原因,并指出同样的隐患也适用于超出其锚点的 overlay 内容。25

要在所有内容(包括各种 sheet)之上呈现一个全屏遮罩层,没有任何干净的纯 SwiftUI 方案。专家组的建议是借助 UIKit 场景生命周期降到一个新的 UIWindow,其更深层的原则是:最后一个窗口胜出,并且对于”什么位于最上层”必须有一个单一的事实来源。26 他们还补充道,更好的修复办法往往是恢复用户所期望的导航栈,而不是给所有内容粗暴地盖上一层。26

应该先采用什么

一次以机制方式讲解的发布,回报的是按杠杆效应而非按新颖程度来排序的做法。

  1. 在您的 AppKit/UIKit 代码中把模型类切换为 @Observable 它会删掉手动的 needsDisplay 调用,为每一个进行观察的绘制和布局方法带来自动重绘,并且是让日后 NSHostingView 的嵌入变得轻而易举的前提条件。814 这是这三场讲座中风险最低、即时回报最高的一招。
  2. 审查惰性栈中是否存在动态变化的子视图数量。 任何条件性地返回零个或一个子视图、或在其 body 中解包可选值的 ForEach 叶子节点,都在按索引保持视图存活;把过滤移到 Query 上的 Predicate 里(或层级中更高的位置),内存占用和滚动到指定项的性能都会改善。1
  3. 把视图设置从 onAppear 中移到初始化器里。 预取只有在预取好的工作能存活下来时才有用;在 onAppear 中改变尺寸或内容的设置会把那份工作丢弃掉。1 这是一个悄无声息、覆盖面广的滚动流畅度提升。
  4. 用相对可见性 API 取代绝对滚动偏移量的读取。 任何绑定到内容偏移量的逻辑都会漂移;.onScrollTargetVisibilityChange 绑定的是哪些行实际可见。21
  5. 只在一个小效果配得上它的位置时才去使用着色器。colorEffectdistortionEffect 开始;只有当某个效果必须采样相邻像素时才升级到 layerEffect,并用 TimelineView 的时间戳来驱动任何运动,而不要指望着色器内部有状态。4567

贯穿这三场的那条主线是:在向框架施加压力之前,先预测它的行为。惰性栈靠估算,着色器会遗忘,互操作是一道衔接缝、而非一次重写。带着这三条事实去构建,其余的自然水到渠成。

FAQ

为什么我的 SwiftUI 惰性栈滚动位置会跳动或漂移?

因为 LazyVStack 不加载屏幕外的视图,它会估算这些视图的高度以及可见区域上方的空间,所以绝对内容偏移量是一个估算值,框架会在了解到真实布局后对其进行修正(例如在一次方向切换之后,当您滚回顶部时它会调和这个估算值)。1 如果您把 UI 绑定到绝对偏移量上,阈值就会随着估算值的稳定而漂移。请改用 .onScrollTargetVisibilityChange,它基于哪些子视图实际可见来触发。2

在 iOS 27 中,我该如何让 SwiftUI 惰性栈的滚动保持流畅?

让预取发挥它的作用:在视图的初始化器中完成设置,这样惰性栈在视图出现之前所做的工作就不会被丢弃,并且避免在 onAppear 中改变视图的尺寸或内容。1 同时避免在 ForEach 叶子节点中出现数量动态变化的子视图,并避免在视图出现后再做布局改动(比如由 onGeometryChange 驱动的高度变化),因为这两者都会迫使栈重做工作或在滚动途中重新计算位置。1

我应该在什么时候使用 colorEffectdistortionEffect 还是 layerEffect

colorEffect 根据每个像素的位置和原始颜色来变换它的颜色(比如一个灰度滤镜)。4distortionEffect 来做几何效果,您在其中把一个输出位置映射到一个用于采样的源位置。5 当输出像素取决于不止一个输入像素时用 layerEffect,因为它把整个视图图层交给着色器去采样相邻像素或整个区域,而这正是模糊和域扭曲所需要的。6

我该如何在 SwiftUI 中为一个 Metal 着色器做动画?

着色器是无状态的:它们不保留上一帧的任何记忆,只取决于自己的参数,所以您无法从着色器内部做动画。3 请喂进去一个随时间变化的值。一个处于动画时间表上的 TimelineView 会在每一帧触发并附带一个时间戳;把那个时间戳作为参数传入着色器(讲座把它加到了噪声采样位置上),效果就会动起来。73

我能否在不重写的情况下,把 SwiftUI 添加到一款现有的 AppKit 或 UIKit 应用中?

可以,而且讲座明确指出,没有哪款应用必须完全用 SwiftUI 才能从中受益。8 把您的模型标记为 @Observable,让 AppKit 和 UIKit 自动重绘,然后用 NSHostingView 嵌入 SwiftUI 视图,用 NSGestureRecognizerRepresentable 把现有的手势识别器带过来,用 NSHostingMenu 构建菜单,并用 NSHostingSceneRepresentation 从您的 app delegate 中附加 SwiftUI 场景;所有这些都已随 2026 年的各版本或更早版本一同发布。816171819

完整的 Apple Ecosystem 系列:SwiftUI 的底层构造(结果构建器、不透明类型、值类型的视图树),它解释了为什么一个惰性栈会把视图结构体解析成另一组不同的子视图;iOS 27 SwiftUI 表层(重排序、文档、工具栏、错误处理),这篇关于性能与互操作的文章就置于它的旁边;@Observable 内部机制,它如今也在 AppKit 和 UIKit 中驱动着自动重绘;以及 Liquid Glass 模式,互操作讲座把它的跨框架实现归功于共享的 SwiftUI。本系列的中枢是 Apple Ecosystem 系列。若想了解更广泛的”iOS 结合 AI 智能体”背景,请参阅 iOS Agent 开发指南

References


  1. Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI.” developer.apple.com/videos/play/wwdc2026/321. Covers lazy-stack layout and height estimation, the estimated content offset, view-struct-to-subview resolution, the dynamic-subview-count trap, prefetching across frame deadlines, and onAppear versus initializer setup. 

  2. Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI”. The session presents onScrollTargetVisibilityChange (a modifier whose closure runs when the set of visible scroll targets changes) as the stable, relative-visibility alternative to absolute content-offset reads. 

  3. Apple, WWDC26 session 322, “Compose advanced graphics effects with SwiftUI.” developer.apple.com/videos/play/wwdc2026/322. Frames effects as a composable pipeline; covers blur, the three shader entry points, the NoiseTexture domain-warp technique, stateless shaders driven by time, and alignment-guide attachment. 

  4. Apple Developer Documentation: colorEffect(_:isEnabled:). Returns a new view that applies a shader transforming each pixel’s color, given its position and original color. 

  5. Apple Developer Documentation: distortionEffect(_:maxSampleOffset:isEnabled:). Applies a shader that maps the position of each pixel to a source position to sample from, for geometric effects. 

  6. Apple Developer Documentation: layerEffect(_:maxSampleOffset:isEnabled:). Applies a shader as a layer effect with access to the entire view layer, allowing each output pixel to sample multiple input pixels. 

  7. Apple Developer Documentation: TimelineView. A view that updates its content according to a schedule; on an animation schedule it supplies the per-frame timestamp session 322 feeds into its shader. 

  8. Apple, WWDC26 session 272, “Use SwiftUI with AppKit and UIKit.” developer.apple.com/videos/play/wwdc2026/272. Covers automatic @Observable redraw in AppKit/UIKit, observation back-deployment via Info.plist, Canvas, NSHostingView, NSGestureRecognizerRepresentable, NSHostingMenu, and NSHostingSceneRepresentation

  9. Apple Developer Documentation: NSGestureRecognizerRepresentable. A protocol that wraps an NSGestureRecognizer for use as a SwiftUI gesture, implemented with makeNSGestureRecognizer and handleNSGestureRecognizerAction

  10. Apple Developer Documentation: MenuBarExtra. A scene that renders a menu bar item; session 272 attaches it to an existing AppKit app through NSHostingSceneRepresentation

  11. Apple Developer Documentation: scrollTransition(_:axis:transition:). Applies a transition as a view scrolls within a scroll view; session 321 warns that a transform pushing a view into the visible rect can desync a lazy stack. 

  12. Apple Developer Documentation: onChange(of:initial:_:). Runs an action when a value changes; session 322 uses it to recenter the current transcript line. 

  13. Apple Developer Documentation: alignmentGuide(_:computeValue:). Sets a view’s alignment guide so the layout system positions it semantically; session 322 overrides a bottom guide to attach a subview’s top edge to its container’s bottom edge. 

  14. Apple Developer Documentation: Observable. The macro that makes a class’s mutable properties participate in the Observation system, which AppKit and UIKit track for automatic redraw in the 2026 releases. 

  15. Apple Developer Documentation: Canvas. An immediate-mode drawing view whose closure receives a GraphicsContext; session 272 uses it to redraw the circular color picker and notes withCGContext for reusing Core Graphics code. 

  16. Apple Developer Documentation: NSHostingView. An NSView subclass that hosts a SwiftUI view hierarchy inside an AppKit view tree. 

  17. Apple Developer Documentation: NSViewRepresentable. A wrapper that lets an NSView participate in a SwiftUI view hierarchy; session 272 names it alongside NSGestureRecognizerRepresentable as part of the representable family. 

  18. Apple Developer Documentation: NSHostingMenu. An NSMenu subclass that renders a SwiftUI view as menu content, added to the main menu as the submenu of an NSMenuItem

  19. Apple Developer Documentation: keyboardShortcut(_:modifiers:). Assigns a keyboard shortcut to a control’s action; session 272 adds one to the menu button so input devices that cannot force-click still reach the feature. 

  20. Apple, WWDC 2026 UI Frameworks group lab, session 8002. Paraphrased from a locally transcribed recording; no official transcript is published. The team clarified that moving code from body into a computed property is a readability change only, and that the independent-invalidation boundary appears when you extract a separate view type. 

  21. Apple, WWDC 2026 UI Frameworks group lab, session 8003. Paraphrased from a locally transcribed recording; no official transcript is published. The team noted that every environment change invalidates all views reading that value, and advised keeping fast-changing values (the panel’s example was the current time) out of the environment. 

  22. Apple, WWDC26 session 8006, “SwiftUI Group Lab.” developer.apple.com/videos/play/wwdc2026/8006. Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab; Apple publishes no official captions for the labs. Source for partial graph evaluation in leftover frame time (and the onAppear-resize warning, with sizing done in init), the UIKit-top-down-versus-SwiftUI-bottom-up interop “sandwich or cake” arrangement, and the scroll-position-in-the-environment example that invalidates every reader on every frame. 

  23. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the onGeometryChange transform-gates-action mechanism (return a coarse value to fire at thresholds) and the guidance to confine an expensive GeometryReader to a background. The two-closure shape is documented at onGeometryChange(for:of:action:): the of: transform closure derives an Equatable value from the geometry proxy and the action: closure runs only when that value changes. 

  24. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for replacing most onChange work with a dynamic property that vends a cached value synchronously. Apple’s DynamicProperty protocol defines an update() method that SwiftUI calls immediately before rendering a view’s body so the property holds its most recent value. 

  25. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the culling of views that draw outside their reported layout bounds inside a ScrollView (the failure behind overflowing custom dropdowns and overlays), and the note that the same hazard applies to overlay content extending past its anchor. 

  26. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the lack of a clean pure-SwiftUI full-screen-over-sheets overlay, the drop to a new UIWindow through the UIKit scene life cycle, the “last window wins / single source of truth” principle, and the preference for restoring the navigation stack over a disruptive cover. 

相关文章

iOS 27 的无障碍设计:阅读类 App 与自定义控件

iOS 27 针对阅读类 App 与自定义控件的无障碍设计:文本导航链接、causesPageTurn、UITextInput、adjustable 特征与直接触摸。

2 分钟阅读

iOS 27 中 SwiftUI 的新变化

iOS 27 重构了 SwiftUI 的列表、文档、工具栏与错误处理:拖拽重排序、可读写的文档模型、工具栏溢出,以及基于条目的弹窗。

10 分钟阅读

从76到100:实现Lighthouse满分

一个个人作品集网站如何从移动端Lighthouse性能评分76分、CLS为0.493,提升到所有类别均达到100/100/100/100的满分。

3 分钟阅读