第一次看到 ArrangementView 的 API 时,我有一种说不出的别扭感。直到在 Xcode 27.1 beta 中实际使用后,这种感觉才逐渐清晰。查阅更多的苹果资料,再把它放回 iPhone Duo 的使用场景,我发现这份别扭其实来自两个方面:一部分源于我对它的定位不够清楚,理解之后便释然了;另一部分则来自 API 本身,即便想明白了,依然存在。
因此,想用好 ArrangementView 需要在使用前多想一步:内容之间究竟是什么关系?哪些结果可以交给容器,哪些取舍仍须由应用承担?谋而后定。
本文并非
ArrangementView的教学分享,而是我学习和理解这个 API 时的一些体会。阅读前最好对它有基本了解。
非典型容器
与常见的布局容器不同,ArrangementView 想解决的并非任意两个视图的排列问题。它把两个视图之间的关系放进一组布局规则中,再结合可用空间、宽高比、尺寸类别及设备分割区域,决定视图是否显示、显示在哪里。苹果把这种从输入到布局结果的映射称为 arrangement。
使用它时,开发者需要从几个维度来理解它:
- 声明关系,而非指定几何。 这点与
NavigationSplitView有些相似。开发者声明谁是 primary、谁是 secondary,选择 split 或 overlay;具体几何则由容器结合环境决定。要预期最终结果,必须先理解它的布局与退化规则。 - 可见性需要纳入内容设计。 在 split 中,如果无法沿指定轴拆分,容器可能只呈现 primary。这既是布局上的取舍,也是信息与交互上的取舍。secondary 再次出现时仍可保留原有状态;但在它暂时不可见期间,所承载的信息如何抵达用户,仍需开发者考虑。
- 配置是建议,而非最终结果。
splitArrangementLayoutRatio、splitArrangementFixedLayoutSize表达尺寸偏好或约束;overlayArrangementEdge则作用于 overlay 转为并排时的位置。它们分散在子视图上,也并非在每一种排列结果下都会生效。开发者不仅需要理解这些配置本身,还要理解容器最终会如何采用它们。
从某些角度看,ArrangementView 更接近 NavigationSplitView 这类预设了自适应行为的容器。然而,后者有清楚的导航语义、可见性状态,以及退场后的处理方式(在紧凑宽度下,它会折叠为单列导航栈);前者要表达的内容关系更宽泛,语义锚点也就更难确定。
难以确定的主次
ArrangementView 的取舍发生在视图层级:谁暂时不出现,牵涉的不只是大小,还有信息和交互能否继续被访问。
在 split 关系下,primary 的含义很直观:当约束使分割无法成立时,它是被优先保留的视图。
到了 overlay,同一个名字却承担了另一种职责:当容器采用叠放布局时,primary 位于 secondary 上方。这与许多人熟悉的 overlay modifier 的语感并不一致。后者是在原视图之上添加一层,原视图位于下方;而 ArrangementView 的 overlay 则用 primary 指代上层。
这意味着,一个视图若在 split 中是应被保留的主体,在 overlay 中未必也是合适的前景。
苹果自己的示例也体现了这一点:从 split 改为 overlay 时,它交换了 PlayerView 与 UpNextView 的 primary、secondary 位置。
问题不在于哪条规则不合理,而在于两套规则借用了同一对主次名称,却要求我们根据不同关系重新赋义。primary 和 secondary 看似建立了一套统一的内容关系,实际上它们的含义仍取决于当前采用的是哪一种 arrangement。
一个容器,两种逻辑
将 split 与 overlay 放进同一个容器,并非全无道理:overlay 遇到活动分割区域时也可能转为并排,在几何结果上与 split 很相似。
但几何上的相似,并不意味着内容关系也相同。
这种差异在切换 arrangement 时尤其明显。如果产品需要在 split 与 overlay 之间切换,开发者仍要自行决定何时切换、是否交换主次,以及如何维持内容状态。
在 SwiftUI 中,.split 与 .overlay 是不同的具体类型。切换意味着 if else,视图标识也随之改变;即便通过自定义 ArrangementViewStyle,在 makeBody 内部按值选择,本质上也只是另一层 if else 的包装,添加动画后得到的仍只是 transition。
在 UIKit 中使用 updateArrangement(_:animated: true) 测试时,尽管容器实例没有改变,结果同样是硬切(Xcode 27.1 beta)。
至少在当下,split 与 overlay 之间并不存在连续的过渡。它们被统一进了同一个容器,却没有因此成为一种连续的布局状态。从使用体验上看,ArrangementView 更像是把 ArrangementSplitView 与 ArrangementOverlayView 合并进了同一个容器。
这也解释了为什么初次接触这个 API 时容易感到复杂:表面上,我们只是在一个容器里选择不同 arrangement;实际上,我们仍在处理两套不同的内容逻辑,以及只在各自模式下有效的配置和环境值。
缺少集中的信息源
既然容器替我们决定了最终布局,一个很自然的需求便是:子视图能否知道这个决定最终变成了什么?
围绕 ArrangementView,苹果提供了 overlayArrangementZIndex、splitArrangementAxis,以及用于查询设备保留区域的 reservedRegions。这些值各有用途,但它们提供的更多是布局过程中的局部信息,而不是容器解析后的完整结果。
开发者可以知道 split 的轴向、overlay 的层级,也可以查询设备的保留区域,却很难直接回答一个更高层的问题:当前用户看到的究竟是哪一种排列结果?
例如,overlayArrangementZIndex 的值为 0,本身无法区分视图是位于底层,还是 overlay 已经转为并排;splitArrangementAxis 表达的是 split 的轴向,而不是完整的可见性状态。开发者当然可以结合更多上下文进行推断,但这意味着应用需要自己重新组织这些零散信号。
我会更希望看到一个由容器解析后的集中状态:比如 split(附轴向)、overlay(附层级),以及当前只呈现 primary 的情况。
重点并不在于是否一定要增加这样一个枚举,而是 ArrangementView 目前缺少一个集中表达最终结果的接口。它告诉了子视图不少影响布局的信息,却没有直接告诉它们容器最后做出了什么决定。
这件事在简单场景中并不明显。一旦子视图需要根据最终排列调整内容,应用就不得不重新推断容器已经完成的判断。
ArrangementView 对设备的空间特征很敏感,却没有把产品所需的场景判断一并收拢。容器替我们处理了位置,应用仍须维护内容状态与模式语义。这让它作为通用容器显得有些复杂,作为特定场景容器又显得不够集中。
它的舒适区,恰好也是边界
如果把 ArrangementView 当作通用容器,上述成本很容易被放大;但放到 iPhone Duo 上,一些看似特别的规则立刻有了具体的用途。
折叠形成新的可用区域,两个相关视图需要避开分割处,并重新分配空间。ArrangementView 替应用维护了这组难以用普通栈容器直接表达的规则,使用起来十分自然。
这也回答了最初那份“别扭感”中属于定位的那一部分:有些问题并不是 API 没有解决,而是我一开始期待它解决的范围太大。
但找到舒适区,并不意味着它一定应该成为优先选择。
苹果仍建议优先选择最高层级的抽象:先是标准的自适应组件,其次才是 ArrangementView,再往下是保留区域,铰链数据则只在确有必要时使用。
我认为,只有当界面需要自定义的分割或叠放时,才值得考虑 ArrangementView。为此,我列了几个更具体的问题:
- 两个视图是否有稳定的“主内容/细节”或“前景/背景”关系?
- 空间不够时,谁可以退场;发生重叠时,谁应该在上?这两个答案能否共用一套主次语义?
- 子视图是否只需少量调整就能适应最终结果,还是大部分内容都需要自行识别并重建当前模式?
- 系统针对折叠区域提供的编排能力,是否确实省去了应用原本需要维护的布局判断?
如果这些问题的答案都很清楚,尤其当界面原本就接近苹果所展示的两类关系时,ArrangementView 会是一把相当顺手的工具。反之,如果答案含混,或者关系之间的切换本身就是产品的核心逻辑,我更愿意从 HStack、VStack、ZStack、标准导航容器或自定义布局出发。
这样做并没有错过某种“更先进”的写法,只是把决定留在了最了解内容的地方。
ArrangementView 的不顺手,来自它把很具体的设备问题包进了一个看似通用的双视图容器;而它的优雅也恰恰在这里:一旦内容关系与设备规则吻合,那些原本需要应用承担的复杂性,便可以交给系统。
使用之前,多想一步;想明白了再用。谋而后定。