前言同一款播放器在窄窗口里可能需要上下排列画面和队列获得更宽空间时可以让它们左右并排。到了折叠设备问题又多了一层内屏即使尺寸没有明显变化中央的折叠区域也可能影响控件应该待在哪里。ArrangementView处理的正是两块相关内容如何共同适应环境的问题。理解它不能只比较三个 API 名称还要看同一份内容在外屏、全展开内屏、半展开内屏中的位置、尺寸和显示方式。本文以 Apple 的 Strike a pose with adaptive layouts on iPhone Duo 为线索使用一个风景播放器演示蓝色 A 始终代表风景内容橙色 B 始终代表队列或控制台。两块区域的颜色和业务身份保持稳定布局变化因而更容易观察。更新日期2026 年 9 月 25 日。本文对应当前ArrangementViewTest工程的新界面使用 Xcode 27.127A9269编译。外屏三种模式已有运行截图内屏全展开、半展开及旋转尚未完成实测。后文会分别说明实现、已观察结果和待验证预期。一、ArrangementView 负责两块内容的关系ArrangementView接收primary与secondary。系统根据可用空间、Size Class 和硬件环境决定如何安排内容。默认样式为.automatic当前解析为 split arrangement官方文档标注 iOS / iPadOS 27.1 起可用。ArrangementView 文档本文把职责分成三层层级本例中的工作ArrangementView决定 A/B 的分配区域、排列和显示行为面板内部视图在分配到的区域中绘制风景、滚动队列、显示控制条实验说明与诊断展示尺寸、活动分隔区域和当前模式的观察目标这意味着本文没有用“宽度超过某个数就创建 HStack”的方式冒充 ArrangementView也没有用按钮模拟折叠后再手动摆放两个面板。三种模式切换的是 arrangement 策略外屏、内屏和折叠姿态则由真实运行环境提供。如果业务需要自行测量和放置任意数量的子视图仍可以实现Layout。它提供的是更底层的几何布局能力。Layout 文档二、先认识三个演示避免“切了模式却不知道在看什么”新版界面用风景画面与队列形成明显对照。风景由 SwiftUI 的渐变和 Path 绘制没有图片资源、网络服务或第三方依赖。播放按钮只改变 UI 状态不播放真实音频。页面选项使用的样式重点观察双面板.split蓝色 A 与橙色 B 如何上下、左右重排主辅切换.split.axes(.horizontal)不允许上下分割时B 是否退让A 是否获得更多空间浮动控制台.overlayB 是叠在 A 上的控制条还是获得独立区域的完整控制台页面顶部始终保留队列入口。即使 B 暂时不可见也能在弹窗中选择内容。playing和selectedTrack保存在 arrangement 外部模式切换不会主动创建一套新的播放状态。这里还要区分业务身份和API 角色模式primarysecondary双面板、主辅切换A风景播放器B播放队列浮动控制台B前景控制台A背景风景A/B 标识用于帮助读者追踪同一块内容并不固定等同于 primary/secondary。尤其在 overlay 中前景控制台才是 primary。overlay 文档三、双面板看系统如何重新分配空间当前工程中的核心代码如下ScenicPane和TrackPane的完整实现见文末ArrangementView{ScenicPane(mode:mode,selectedTrack:selectedTrack,playing:$playing).measurePane(A)}secondary:{TrackPane(selectedTrack:$selectedTrack).measurePane(B)}.arrangementViewStyle(.split)在官方视频的演示里容器偏宽时左右分割偏高时上下分割实际决策还受到环境影响。视频12:00 起本次外屏实测中A 和 B 上下排列各自获得382 × 211 pt。这次显示的是每块面板自己的 GeometryReader 尺寸而不是包含标题、说明文字在内的整个页面高度。到了全展开内屏重点应是“每块内容现在获得了多少空间、排列是否变化”。不能预设内屏一定左右排列旋转、分屏、系统栏以及页面自己的说明区域都会改变 arrangement 的可用比例。半展开时还要观察面板边界与活动 division 的关系。不要仅凭截图更宽就判断折叠适配已经发生。比较时应同时记录运行姿态、活动区域及面板尺寸。容器自适应不代表面板内容可以固定尺寸新版风景按面板宽高绘制队列则在 B 内部滚动。因此当 A 被压缩成横向短面板、B 的高度减少时内容仍能利用自己的区域。ArrangementView 本身位于导航容器内部而不是被包进滚动列表滚动发生在面板内部。视频容器使用边界如果内容有明确约束可以在相应面板上设置splitArrangementLayoutSize或splitArrangementLayoutRatio。本例不添加比例约束以便先观察系统的默认分配。比例偏好还会受到优先级和剩余空间影响不能理解成每种姿态下都严格成立的固定比例。比例 API 文档四、主辅切换限制方向观察 B 如何退让第二个模式只改变允许的分割方向ArrangementView{ScenicPane(mode:mode,selectedTrack:selectedTrack,playing:$playing).measurePane(A)}secondary:{TrackPane(selectedTrack:$selectedTrack).measurePane(B)}.arrangementViewStyle(.split.axes(.horizontal))它表达的是“允许水平分割”并不意味着空间不足时仍必须挤出两列。官方视频中当主方向为纵向而样式只允许水平分割时只显示主内容。视频12:41本次外屏结果非常直观橙色 B 不再显示蓝色 A 从382 × 211 pt扩大到382 × 422 pt。风景在同一窗口里获得了双倍高度读者可以直接看到“限制方向”的结果。接着应在全展开内屏和旋转后检查 B 是否重新出现。这里的测试对象是系统分配结果不是自行维护一个isDuoExpanded布尔值来隐藏队列。B 的退让也解释了为什么保留独立队列入口很重要布局可以收起一块面板但不应同时删除用户选择内容的能力。五、浮动控制台从叠放到独立区域第三个模式把橙色控制台设为 primary把蓝色风景设为 secondaryArrangementView{AdaptiveConsole(compact:panesOverlap,selectedTrack:$selectedTrack,playing:$playing).measurePane(B).overlayArrangementEdge(HorizontalEdge.trailing).overlayArrangementEdge(VerticalEdge.bottom)}secondary:{ScenicPane(mode:mode,selectedTrack:selectedTrack,playing:$playing).measurePane(A)}.arrangementViewStyle(.overlay)overlay 可以先让内容叠放再在环境变化时将内容分开排列。边缘修饰符指定转为相应方向布局时的位置偏好这里明确区分水平 trailing 和垂直 bottom 两个重载。ArrangementView 文档 · 边缘修饰符在本次外屏截图中A 占据背景B 是带橙色边框的浮动控制条。它与“主辅切换”不同B 没有消失而是与 A 使用重叠的空间。当系统将两个分配区域分开时新版代码会把 B 展开为独立控制台显示实际尺寸、完整可滚动队列、播放按钮和下一段按钮。这个分离分支已经实现并通过编译尚未取得半展开内屏的运行截图不能写成已经实测成功。官方视频使用环境值本例观测实际几何视频通过overlayArrangementZIndex调整内容的紧凑状态。这个环境值描述 arrangement 中的叠放层级不是设备折叠角度。环境值文档早期实验里不同读取层级曾出现不同读数用户后续截图中的splitArrangementAxis也与先前运行观察不同。这些观察不足以证明 API 存在通用缺陷更不能建立“某个 axis 值就代表两个面板都可见”的规则。新版因此不再把 axis/z-index 直接作为页面上的布局结论。本例采用另一种可检查的方法在同一命名坐标空间中测量 A/B 的分配矩形判断两者是否相交。这个判断只改变控制台内部内容不负责把 A/B 摆到哪里。privatestructPaneFrames:PreferenceKey{staticvardefaultValue:[String:CGRect]{[:]}staticfuncreduce(value:inout[String:CGRect],nextValue:()-[String:CGRect]){value.merge(nextValue(),uniquingKeysWith:{_,newinnew})}}privateextensionView{funcmeasurePane(_name:String)-someView{background{GeometryReader{proxyinColor.clear.preference(key:PaneFrames.self,value:[name:proxy.frame(in:.named(arrangementStage))])}}}}在 arrangement 外部建立坐标空间并接收观测结果privatevarstage:someView{arrangement.coordinateSpace(name:arrangementStage).onPreferenceChange(PaneFrames.self){frames$0}.clipped()}然后计算重叠状态privatevarpanesOverlap:Bool{guardletaframes[A],letbframes[B],a.width1,b.width1else{returntrue}letintersectiona.intersection(b)return!intersection.isNullintersection.width2intersection.height2}首次布局尚未得到有效矩形时默认采用紧凑控制条两个矩形在两个方向上都有超过 2 pt 的交集时视为重叠。2 pt 是这个演示的几何容差不是 Apple 规定的折叠阈值。控制台外层始终填满系统分配的区域内部才在紧凑条与完整队列之间切换。这样可以减少“内容变大导致测量变大、测量变大又改变内容”的反馈。播放状态与选中项目保持在父视图中不因内容分支切换而主动重置。这个办法也有明确边界矩形交集不是通用可见性检测无法判断 opacity、复杂裁剪或遮挡顺序过渡动画中还可能出现短暂相交。本例只用它区分两个 overlay 面板的分配关系不用它推断设备姿态也不用它判断主辅模式中 B 是否可见。推广到生产界面前应验证动画、旋转和折叠全过程。六、外屏、全展开、半展开哪些信息能判断哪些不能新版页面会查询当前视图范围内的 divisionletregionsproxy.reservedRegions(kind:.division,options:.includeInactive,layoutDirectionBehavior:.fixed)letactiveregions.filter(\.isActive).includeInactive让示例显式纳入非活动区域再通过isActive分类避免依赖资料中存在差异的默认查询描述。includeInactive 文档页面只给出与查询证据相符的说明查询结果页面显示不能据此推断的内容存在活动 division活动折叠区域观察两侧避让具体折叠角度和所有控件是否已避让存在 division但未激活折叠区域未激活观察连续画布所有设备和窗口条件下都代表全展开没有返回 division当前窗口没有折叠分隔区域一定是外屏、一定是普通 iPhone因此“外屏”“全展开内屏”“半展开内屏”是测试者在 Simulator 中设置并记录的场景不是这段区域查询代码完整识别出的三种设备状态。粉色标记只画出系统返回的区域点击状态行右侧的标记按钮可以开关活动 division 的粉色覆盖层。区域在实验舞台自己的 GeometryReader 中重新查询避免把外层容器坐标直接拿来画在内部视图上。这里使用.fixed按照返回的固定几何坐标绘制调试层真正的内容布局仍由 ArrangementView 处理。粉色层禁用命中测试不拦截按钮也不会通过额外 padding 再避让一次铰链。需要自行实现 RTL 布局时应仔细核对 API 的镜像语义。reservedRegions 文档.division与.occlusion也不能混为一谈前者表示分隔后者表示遮挡。当前粉色层只显示 division不是摄像头轮廓也不是所有安全区域的可视化。七、摄像头遮挡修复把入口放在可控的内容区域此前的实际截图中右上角工具栏的队列按钮与摄像头重叠。把工具栏项改成.topBarLeading后系统仍可能根据 Duo 的布局规则将栏内容重新排列不能只凭 placement 名称判断物理位置。新版把队列入口放到模式选择器左侧位于页面内容内部弹窗的“完成”按钮则放在底部安全区域.safeAreaInset(edge:.bottom){Button(完成){dismiss()}.buttonStyle(.borderedProminent).frame(maxWidth:.infinity,minHeight:44).padding(8).background(.bar)}这是一项针对演示界面的布局修正不是自行探测摄像头后计算位移的通用方案。当前外屏截图已检查入口位置但所有内屏姿态、RTL 和大字号仍需继续验证。不要把它扩展成“工具栏都不应该使用”的结论。Apple 的设计指导强调可调整尺寸、边距与安全区域以及不同姿态下的功能连续性本文保留独立队列入口也是出于这个目的。Design for iPhone Duo八、验证记录与接下来的截图矩阵当前ArrangementViewTest已用 Xcode 27.1 编译成功并在 Duo / iOS 27.124A94401运行。构建日志保存在配套目录的evidence/current-project-build.log中。现有 AppIntents 警告表示未依赖相应框架而跳过元数据提取与本例布局功能无关。场景本次状态证据或待观察内容外屏双面板已运行并截图A/B 上下排列各 382 × 211 pt外屏主辅切换已运行并截图仅显示 A382 × 422 pt外屏浮动控制台已运行并截图A 为 382 × 422 ptB 是重叠控制条全展开内屏三种模式待验证A/B 尺寸与排列覆盖模式是否仍叠放半展开内屏三种模式待验证division 活动标记、面板位置、控制台分离分支内屏旋转、分屏、外内屏切换待验证页面重排与播放状态连续性普通 iPhone / iOS 27.1未验证当前安装的 runtime 不支持创建相应对照设备面板尺寸是这次截图的结果不是实现中的断点也不是对所有 Duo 的固定规格。两种姿态得到相同布局是可能的示例不能为了“看起来有变化”而人为强制排列。推荐的实际操作顺序在外屏依次选择三种模式对照本文截图确认 A/B 的含义和变化。将设备切到全展开内屏保持同一模式和内容记录面板尺寸、division 和布局。让内屏部分折叠重点查看粉色区域与面板边界在覆盖模式中检查 B 是否从浮动条变成独立控制台。旋转设备后重复确认没有把“横屏”和“水平分割”混为一谈。播放暂停、选择另一段内容再切换模式与姿态检查业务状态是否保留。最后测试大字号和弹窗避免只验证静态画面。不要通过调整 SwiftUI frame 或直接写入环境值冒充真实折叠测试。需要在 Simulator 的姿态控制中改变设备状态再记录真实结果。截图前先确认显示端口和目标设备/Applications/Xcode27.1.app/Contents/Developer/usr/bin/simctl io booted enumerate /Applications/Xcode27.1.app/Contents/Developer/usr/bin/simctl io booted screenshot--display1screenshot.png1是本次外屏截图所用编号内屏应以枚举结果为准。同时启动多台模拟器时请将booted换成目标 UUID。截图说明应包含模式、外内屏、姿态、运行时版本和区域信息。九、工程结构与官方阅读路线当前项目保留原有 App 入口ArrangementViewTestApp.swift应用入口。ContentView.swift系统可用性检查与预览。ArrangementLab.swift三个演示、面板几何观测、区域标记和说明弹窗。配套压缩包中的独立ArrangementLab.xcodeproj也已同步同一套实现便于读者单独打开其Sources/ArrangementLabApp.swift是上述三份文件的合并版本。两个工程择一运行即可。本文截图来自当前ArrangementViewTest工程。建议在官方主视频中依次查看 6:39 区域查询、11:17 创建容器、13:21 覆盖布局并与本文的外屏截图和内屏待验证矩阵对照。API 详情可以继续查阅 SwiftUI 更新、样式修饰符、splitArrangementAxis 和 Xcode 27.1 发布说明。实现中的几何观测属于本文的演示逻辑应与系统 API 的保证区分开。那么感谢宝子们的观赏我们下次不见不散哦