news 2026/9/26 6:06:48

iPhone Duo 从外屏到内屏:ArrangementView 抢先适配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
iPhone Duo 从外屏到内屏:ArrangementView 抢先适配

前言

同一款播放器,在窄窗口里可能需要上下排列画面和队列;获得更宽空间时,可以让它们左右并排。到了折叠设备,问题又多了一层:内屏即使尺寸没有明显变化,中央的折叠区域也可能影响控件应该待在哪里。



ArrangementView处理的正是两块相关内容如何共同适应环境的问题。理解它,不能只比较三个 API 名称,还要看同一份内容在外屏、全展开内屏、半展开内屏中的位置、尺寸和显示方式。

本文以 Apple 的 Strike a pose with adaptive layouts on iPhone Duo 为线索,使用一个风景播放器演示:蓝色 A 始终代表风景内容,橙色 B 始终代表队列或控制台。两块区域的颜色和业务身份保持稳定,布局变化因而更容易观察。

更新日期:2026 年 9 月 25 日。本文对应当前ArrangementViewTest工程的新界面,使用 Xcode 27.1(27A9269)编译。外屏三种模式已有运行截图;内屏全展开、半展开及旋转尚未完成实测。后文会分别说明实现、已观察结果和待验证预期。

一、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,把蓝色风景设为 secondary:

ArrangementView{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{guardleta=frames["A"],letb=frames["B"],a.width>1,b.width>1else{returntrue}letintersection=a.intersection(b)return!intersection.isNull&&intersection.width>2&&intersection.height>2}

首次布局尚未得到有效矩形时,默认采用紧凑控制条;两个矩形在两个方向上都有超过 2 pt 的交集时,视为重叠。2 pt 是这个演示的几何容差,不是 Apple 规定的折叠阈值。

控制台外层始终填满系统分配的区域,内部才在紧凑条与完整队列之间切换。这样可以减少“内容变大导致测量变大、测量变大又改变内容”的反馈。播放状态与选中项目保持在父视图中,不因内容分支切换而主动重置。

这个办法也有明确边界:矩形交集不是通用可见性检测,无法判断 opacity、复杂裁剪或遮挡顺序;过渡动画中还可能出现短暂相交。本例只用它区分两个 overlay 面板的分配关系,不用它推断设备姿态,也不用它判断主辅模式中 B 是否可见。推广到生产界面前,应验证动画、旋转和折叠全过程。

六、外屏、全展开、半展开:哪些信息能判断,哪些不能

新版页面会查询当前视图范围内的 division:

letregions=proxy.reservedRegions(kind:.division,options:.includeInactive,layoutDirectionBehavior:.fixed)letactive=regions.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.1(24A94401)运行。构建日志保存在配套目录的evidence/current-project-build.log中。现有 AppIntents 警告表示未依赖相应框架而跳过元数据提取,与本例布局功能无关。

场景本次状态证据或待观察内容
外屏,双面板已运行并截图A/B 上下排列,各 382 × 211 pt
外屏,主辅切换已运行并截图仅显示 A,382 × 422 pt
外屏,浮动控制台已运行并截图A 为 382 × 422 pt,B 是重叠控制条
全展开内屏,三种模式待验证A/B 尺寸与排列;覆盖模式是否仍叠放
半展开内屏,三种模式待验证division 活动标记、面板位置、控制台分离分支
内屏旋转、分屏、外内屏切换待验证页面重排与播放状态连续性
普通 iPhone / iOS 27.1未验证当前安装的 runtime 不支持创建相应对照设备

面板尺寸是这次截图的结果,不是实现中的断点,也不是对所有 Duo 的固定规格。两种姿态得到相同布局是可能的;示例不能为了“看起来有变化”而人为强制排列。

推荐的实际操作顺序

  1. 在外屏依次选择三种模式,对照本文截图,确认 A/B 的含义和变化。
  2. 将设备切到全展开内屏,保持同一模式和内容,记录面板尺寸、division 和布局。
  3. 让内屏部分折叠,重点查看粉色区域与面板边界;在覆盖模式中检查 B 是否从浮动条变成独立控制台。
  4. 旋转设备后重复,确认没有把“横屏”和“水平分割”混为一谈。
  5. 播放/暂停、选择另一段内容,再切换模式与姿态,检查业务状态是否保留。
  6. 最后测试大字号和弹窗,避免只验证静态画面。

不要通过调整 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--display=1screenshot.png

1是本次外屏截图所用编号,内屏应以枚举结果为准。同时启动多台模拟器时,请将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 的保证区分开。

那么,感谢宝子们的观赏,我们下次不见不散哦!😎

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 6:06:15

opencode Go邀请活动全解析:双向$5奖励机制与实操避坑指南

1. 这个邀请活动到底是怎么回事先把结论摆在前面:opencode Go 这个邀请活动,本质上是官方为了拉新做的一次双向激励——你通过自己的专属分享链接邀请别人注册并登录桌面端,对方拿到 $5 的额度,你自己也能拿到 $5。听起来像是那种…

作者头像 李华
网站建设 2026/9/26 6:06:06

Magisk 自动修补 boot 镜像:从原厂 boot.img 到 systemless Root 的完整指南

简介:这是面向安卓Root玩家的Magisk面具Boot自动修补工具,核心解决传统方式需在手机上手动查找并修补Boot、流程繁琐且容易出错的问题。工具基于电脑端一键式批处理流程,自动产出可刷入的完美面具补丁,并支持随时更换Magisk版本重…

作者头像 李华
网站建设 2026/9/26 6:05:52

自建个人金融服务工作台:从账单接入到预算预警的完整实践

做金融服务相关的东西,我拿到这个标题时第一反应不是那些宏大的行业概念,而是一个更具体的问题:当你真的想给自己或小团队搭一套可用的金融服务体系时,从哪下手?这个标题涵盖的范围太广,从个人记账、账单管…

作者头像 李华
网站建设 2026/9/26 6:04:52

Claude Code 提示词模板库:从架构设计到工作流整合

1. 项目从哪来:为什么我把零散的 Claude Code 提示词沉淀成模板库先说个场景。刚开始用 Claude Code 那阵子,我干过不少重复劳动:每次让它写一个新功能,都要现场组织一大段提示词,把技术栈、目录结构、编码习惯、输出要…

作者头像 李华
网站建设 2026/9/26 6:04:50

FMQL100T国产FPGA开发实战:Procise工具链与硬核应用指南

1. 为什么是FMQL100T?——国产FPGA选型背后的现实逻辑复旦微电子的FMQL系列,尤其是FMQL100T这个型号,在2023—2024年国内高校教学、工业边缘控制和国产化替代项目中突然“冒头”,不是偶然。我去年带一个智能传感器节点开发小组时&…

作者头像 李华
网站建设 2026/9/26 6:04:45

OpenShell不是审批不是容器,而是AI Agent策略边界

1. OpenShell 到底在解决什么问题:一场关于 AI Agent 失控边界的争论这段时间 AI Agent 的概念被炒得火热,从 code interpreter 到 multi-agent 协作框架,各家都在抢这条赛道。但一个很实际的问题摆在面前:你让一个自主 Agent 去执…

作者头像 李华