mcpField
I
iphone-duo-dual-pane-patterns

iphone-duo-dual-pane-patterns

github.com/mirzaaghazadeh设计与 UI250483 次安装今天更新
代码仓库

关于 iphone-duo-dual-pane-patterns

面向 iPhone Duo 双窗格界面开发的实践型设计技能,提供设备尺寸、铰链回调细节,以及窗格内容安排模式。

主要功能

  1. 01设备尺寸与布局参数
  2. 02SwiftUI 和 UIKit 铰链回调
  3. 03将设备姿态映射到布局模式
  4. 04五种双窗格界面模式

适用场景

  1. 01确定界面原型和素材尺寸
  2. 02响应铰链状态变化
  3. 03为编辑器和对比界面选择窗格模式
  4. 04在桌面姿态布局媒体与控件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mirzaaghazadeh/iphone-duo-skills --skill iphone-duo-dual-pane-patterns

安装后在 Claude Code 中输入/iphone-duo-dual-pane-patterns确认已加载。

详细介绍

双栏布局模式、尺寸与铰链回调

相关技能分别介绍了平台:../iphone-duo-adaptive-layout/SKILL.md 中的保留区域和布局方式、../iphone-duo-hinge-and-scenes/SKILL.md 中的铰链和场景,以及 ../iphone-duo-design-review/SKILL.md 中的设计原则。本篇是应用层:制作模型或确定素材尺寸时需要的数值、铰链回调的具体形式,以及双屏设计中每个窗格适合放置哪些内容的成熟模式——这些经验取自 Apple 的资料,以及 Microsoft(Surface Duo)、Google(Android 折叠屏)和 Samsung(Flex Mode)十年来的双屏设计指南;它们最终都得出了相同结论。

数值

以下数据来自 Apple 公布的规格。可用于制作模型、规划素材预算和检查合理性,绝不可用作布局常量。布局应在运行时读取尺寸类别、安全区域和保留区域。

外屏内屏(展开)内屏的每一半
对角线5.4 in7.6 in—
点数(宽 × 高,设备摆放方向)466 × 678 纵向951 × 669 横向475.5 × 669 纵向
像素(宽 × 高,设备摆放方向)1398 × 2034 纵向2670 × 1878 横向≈1335 × 1878 纵向
像素密度460 ppi430 ppi430 ppi
宽高比≈1.45 : 1(高)≈1.42 : 1(宽)≈1.41 : 1(高)
尺寸类别紧凑 × 常规(纵向),紧凑 × 紧凑(横向)常规 × 常规,任意方向—

Apple 将内屏规格列为“1878-by-2670”;设备展开时宽大于高(164.6 × 117.8 mm),因此自然的展开姿态是横向。

请牢记以下三点:

  1. 内屏基本上就是横过来的外屏。 两者的宽高比都约为 1.4–1.45 : 1,这就是 Apple 称它们共用一种宽高比的原因。
  2. 内屏的每一半大致相当于一块外屏。 适合手机屏幕的 UI 可以放进一半,另一半则是额外空间。这就是整个设计理念:一个熟悉的窗格加一个额外窗格,而不是一个拉伸后的手机 UI。
  3. 展开时,折痕沿长边的中线延伸,因此默认的双窗格布局是左右并排,桌面模式布局则是上下排列。
点数

Apple 的 Tech Specs 只列出像素数据。点数尺寸来自 Apple 的 iOS and iPadOS 27 design resource 中的 iPhone Duo 模板(由社区项目 iPhone Duo Adaptive Design Starter Kit 的 Noah Elhadedy 于 2026 年 9 月 23 日重现,并与 Apple 的模板保持一致):

  • 外屏:466 × 678 pt——正好是 1398 × 2034 面板在 3× 下的尺寸。
  • 内屏:951 × 669 pt(纵向时为 669 × 951)。这并不是 2670 × 1878 面板的精确 3× 尺寸:系统会以 2853 × 2007 的尺寸渲染(即 App Store Connect 截图尺寸),然后再缩小,类似 iPhone 6 Plus 的处理方式。内屏素材应按点数尺寸的 3× 导出,而不是根据面板像素导出。
  • 两种方向下,折痕都位于距边缘 475.5 pt 处,即屏幕中心的 951 pt。

仍请在 Xcode 27.1 模拟器中确认;读取 GeometryProxy.size 或场景边界,不要硬编码这些值。

布局令牌(pt)

用于绘制模型图,以及检查系统在运行时提供的值是否合理。来源列很重要:只有 Apple 值来自 Apple 的设计资源;kit 值是社区入门套件中的设计选择,可随意修改。

安全区域 — 设计上不对称;切勿将一侧的值镜像到另一侧。

状态顶部底部前导尾随安全内容尺寸来源
外屏纵向00084(竖向栏)382 × 678Apple
外屏横向00084,位于摄像头一侧594 × 466Apple
内屏横向(平展或书本模式)00084(竖向栏)867 × 669Apple
内屏纵向(平展或桌面模式)84(状态栏 + 顶栏)95(标签栏)00669 × 772Apple

横向栏仅会出现在内屏纵向布局中。其他所有情况下,导航栏、工具栏和标签栏共用一个 84 pt 的竖向栏,从上到下依次为:返回 → 主要操作 → 工具栏组 → 标签栏。

栏和控件

元素尺寸来源
竖向栏宽度84 ptApple
横向导航栏/工具栏(内屏纵向)高 48 ptApple
横向标签栏(内屏纵向)高 95 pt,内嵌Apple
栏按钮/主要栏按钮36 pt/48 ptApple
触控目标默认 44 × 44 pt,最小 28 × 28 ptApple HIG

边距、间距、折痕

令牌值来源
两块显示屏的布局边距20 ptApple
平展时分隔区域的宽度0 ptApple
折叠时分隔区域的宽度未公布 — 读取 ReservedRegion.frame—
折痕避让带(模型图)27 pt,以 475.5 pt 为中心kit
面板间距外屏 16 pt,内屏 24 ptkit
正文最大宽度600 ptkit

列 — Apple 要求列数为偶数;具体列数为 kit 值。

状态列数列间距
外屏纵向416
外屏横向616
内屏纵向624
内屏横向824
内屏书本折叠4 + 4,在折痕处相接,折痕上不设列24

内屏横向安全区域宽度为 867 pt,以下为其中典型的面板宽度(kit):列表 300–320 + 详情 546–566;侧边栏 280;播放器/主面板约 462(一半)+ 上下文面板约 404。面板内容的宽度应接近外屏尺寸,而不是将其拉伸。

物理规格
展开闭合
宽度 × 高度164.6 × 117.8 mm84.1 × 117.8 mm
厚度5.2 mm11.3 mm
重量254 g254 g

生物识别使用侧边按钮上的 Touch ID — 不支持 Face ID。任何提及“Face ID”或使用人脸图标的流程,都需要根据 LAContext.biometryType 分支处理。本设备未列出 LiDAR:基于 RoomPlan 或场景深度构建的功能需要提供备用方案。

转轴回调 — 确切形式

均适用于 iOS 27.1(撰写时为 beta),并已对照 Apple 符号文档验证。

SwiftUI
nonisolated func onHingeChange(
    isEnabled: Bool = true,
    _ action: @escaping (DeviceHingeContext, DeviceHingeContext) -> Void
) -> some View
类型成员成员类型备注
DeviceHingeContexthingeDeviceHinge?nil → 设备没有转轴
DeviceHingeangleAngle使用 .degrees/.radians
DeviceHingestatusDeviceHinge.Status
DeviceHinge.Status.closed、.partiallyOpen、.fullyOpen带静态成员的结构体遵循 Equatable,因此可使用 switch,但需要提供 default

闭包会接收 (old, new)。使用 isEnabled: 在效果不显示在屏幕上时停止更新,而不是在闭包中忽略更新。

UIKit
init(updateHandler: (UIHingeInteraction, UIHingeInteraction.Update) -> Void)
var isEnabled: Bool
类型成员成员类型备注
UIHingeInteraction.UpdatehingeUIHinge?视图离开提供铰链更新的层级时为 nil
UIHingeangleCGFloat弧度,不是角度
UIHingestatusUIHinge.Status
UIHinge.Status.closed、.partiallyOpen、.fullyOpen、.unknownenum(Int 原始值)UIKit 有第四种状态,SwiftUI 没有

UIKit 处理程序不是 (old, new) 这样的成对值。铰链发生变化,以及交互在不同层级之间移动时,都会触发它。如果需要计算差值,请自行保存上一个值。

Apple 在参考文档中说明的规则
  • 状态由系统根据角度和设备方向决定。 不要根据角度阈值重新推导状态——文档没有规定“低于 N° 即为闭合”。
  • 更新速率和精度由系统策略决定,并可能随系统状态变化。不要假定更新频率;应插值或朝最新值进行动画,而不要把每次回调都当作一帧。
  • 如果只需要闭合/部分打开/打开状态,请使用 status,而不是 angle。
  • 角度范围没有文档说明。 不要假定完全展开时恰好为 180°——将 .fullyOpen 视为上限,并以此进行归一化。
可复用的归一化器
/// 0 = closed, 1 = fully open. Effects read this; nothing lays out from it.
@Observable final class HingeProgress {
    var value: Double = 1          // no hinge → behave as if flat
    var isPartial = false
    private var maxSeen: Double = .pi

    func update(_ hinge: DeviceHinge?) {
        guard let hinge else { value = 1; isPartial = false; return }
        let radians = hinge.angle.radians
        switch hinge.status {
        case .closed:
            value = 0; isPartial = false
        case .fullyOpen:
            maxSeen = max(maxSeen, radians)
            value = 1; isPartial = false
        default: // .partiallyOpen
            value = min(max(radians / maxSeen, 0), 1); isPartial = true
        }
    }
}

// usage
.onHingeChange(isEnabled: effectVisible) { _, new in progress.update(new.hinge) }

UIKit 对应的实现会读取 update.hinge?.angle(已经是弧度),并且必须添加一个 case .unknown:,让上一个值保持不变。

姿势 → 模式

没有“笔记本电脑”或“帐篷”状态。姿势描述的是用户的操作;你的代码能看到的是尺寸类别、保留区域和铰链状态。按以下方式映射:

姿势代码观察到的内容模式
闭合外屏、紧凑宽度你已有的手机应用。次要窗格折叠为工作表或推入式页面。
完全展开regular × regular,分隔区域未激活(仅在使用 .includeInactive 时返回)两个窗格并排,媒体内容可以跨越中心区域。
书本式(部分折叠,内屏横向)分隔区域已激活且高度大于宽度左右两个窗格。曲面区域内不放置交互元素。
桌面/笔记本式(部分折叠,内屏纵向)分隔区域已激活且宽度大于高度抬起的一半显示内容,平放的一半显示控件。
帐篷式部分展开,且屏幕通常翻转免手持观看;不要依赖触控。

根据区域而非铰链判断折叠方向:

GeometryReader { proxy in
    let fold = proxy.reservedRegions(kind: .division).first { $0.isActive }
    let isTabletop = fold.map { $0.frame.width > $0.frame.height } ?? false
    // …
}

更好的做法是让 ArrangementView 配合 .split 为你选择轴向——宽度大于高度时水平分割,高度大于宽度时垂直分割;这正是书本式与桌面式之间的判断依据。

五种实用模式

Microsoft 的双屏分类法,与 Apple 的容器相结合:

模式左侧/上方窗格右侧/下方窗格Apple 容器适用场景
伴随窗格画布(照片、房间、文档)工具、图库、属性、提示词ArrangementView(.split)编辑器、创意工具
双视图版本 A(原始版本)版本 B(结果)regular 宽度下的两列布局前后对比、差异比较、比较不同变体
列表-详情列表详情,点击时更新NavigationSplitView浏览目录、样式、消息
扩展画布跨越两个窗格的一张图像—全出血布局 + 用于叠加内容的保留区域地图、全景、视频
桌面式控件媒体,无按钮摇杆、滑块、操作ArrangementView(.split) / .overlay免手持观看、展厅、播放

根据内容之间的关系来选择模式,而不是根据姿势:伴随窗格 = 一个内容及其工具;双视图 = 同一内容的两个版本;列表-详情 = 多个内容及其中一个内容的详情。

来自四家厂商的六条规则
  1. 媒体内容跨折痕,控件分开放。 照片和视频可以跨越折痕。 按钮、文本和输入框绝不能跨越折痕。唯一的例外是正在拖动时——手指按住时,被拖动的卡片可以跨越折痕。
  2. 不要拉伸。 每一半都应呈手机形状。把字体或按钮宽度跨 2670 px 加倍,是典型的错误做法。使用偶数列数,确保没有内容跨越中心线。
  3. 桌面模式:内容朝上,控件朝下。 翘起的一半用于观看,平放的一半用于触控。折痕附近不要放置任何交互元素。
  4. 工具放在画布右侧(或底部)——大多数用户的拇指侧,也是 iOS 27.1 已放置垂直栏的位置。
  5. 两块屏幕 = 对比。 如果应用提供任何前后效果或变体选择,折痕就是天然的分隔线;不再需要单屏滑块。
  6. 状态在折叠时保留。 打开和合上只会调整场景大小,不会将其拆除。滚动位置、正在输入的文本和当前步骤都应保存在模型中,而不是保存在布局切换时会被丢弃的视图 @State 中。对话框应保持在同一半屏内。

由铰链驱动的效果

铰链用于效果和交互,绝不用来控制布局。值得采用的效果:

  • 把铰链当作把手。 将归一化进度映射为揭示效果——门扇打开后展现一个场景、掀起封面、推进镜头。用户的手本身就是动画。将其保持为时长不到一秒的叠加层,绝不阻挡输入;当状态为 .fullyOpen 时,完成过渡并移除叠加层。
  • 合上以保存。 在 .closed 时保存数据,并在外屏显示一条简短确认信息。与反向播放揭示效果自然搭配。
  • 俯仰视差。 将 CoreMotion 倾斜(偏航/翻滚)与铰链进度(俯仰)结合,移动 2.5D 图层——将深度图拆分为 3–4 层就足够了。限制振幅以避免引发晕动症。
  • 连续控件。 任何可以由操纵杆或颤音杆驱动的内容。

始终:

  • 处理铰链为 nil 的情况(大多数设备没有铰链),并为这些用户提供最终状态。
  • 离开 .partiallyOpen 时,在 else 分支中重置状态,避免效果停留在过时的角度。
  • 遵循减少动态效果设置——用交叉淡化替代揭示效果。
  • 效果不在屏幕上时,传入 isEnabled: false。

购买和付费墙界面

  • 表单会自动避开折痕,因此表单付费墙会显示在一半屏幕内。若要跨越两个窗格,请使用自定义双栏布局呈现全屏覆盖层:左侧窗格展示价值主张(最好是用户自己的成果,并带有动画),右侧窗格完整展示套餐和购买按钮。
  • 价格、按钮和法律声明必须放在同一窗格内。 绝不要让价格和按钮分处折痕两侧,也绝不要让按钮跨越折痕。
  • 在外屏上,改用单栏布局;SubscriptionStoreView 会在那里为你处理试用徽章和续订措辞。
  • App Review 3.1.2 仍然适用:计费金额必须是最醒目的元素,试用说明应为次要信息,续订周期必须明确,用户必须能找到恢复购买入口,关闭按钮必须立即可见。
  • 折叠不等于关闭。 付费墙显示期间,不要把合上或打开设备视为用户拒绝,也不要据此触发退出优惠逻辑;场景只是调整了大小。
  • 由桌面模式触发的付费墙应保持桌面模式的布局:预览在上方,套餐在平放的一半屏幕上。

折叠状态感知的引导流程

  1. 绝不要求用户打开设备。 邀请用户“打开设备进入”,如果几秒后用户还没有打开,就继续在外屏上显示内容。人们经常会合上 Duo 使用。
  2. 在任意步骤打开设备,都会将该步骤展开为两个窗格。流程不会重新开始。这是“状态跨折叠保持”原则在引导流程中的应用。
  3. 先让用户体验到“恍然大悟”的时刻,再请求权限或付费。权限预告屏应紧挨着系统提示,并在用户需要时显示,同时提供“暂不”选项。
  4. 在付费墙之后请求通知权限,并结合具体承诺(例如“我们会在试用期结束前提醒你”)。

测试矩阵

在 Xcode 27.1 Device Hub 模拟器中,缓慢拖动铰链滑块,逐步经过每种变化,测试每个双窗格屏幕:

合上书本式(滑块居中)完全展开桌面模式分屏视图,左侧分屏视图,右侧
无黑边☐☐☐☐☐☐
折叠处没有可点击内容—☐—☐——
状态在转换过程中得以保留☐☐☐☐☐☐
铰链效果重置 / 无铰链路径正常工作☐☐☐☐——
付费墙的价格和按钮位于同一窗格☐☐☐☐☐☐

还要在不可折叠模拟器上运行一次,以测试 nil 铰链路径。

评论

支持 Markdown