agentleFS
alexey1312alexey1312/iPhone-Duo-Agent-Skill/skills/iphone-duo-displays/SKILL.md

iphone-duo-displays

Skill6 starsChanged 37 days agoMITView on GitHub

Use iPhone Duo's hinge, two displays, two front cameras and multiple windows in SwiftUI and UIKit apps. Use when a developer asks about the hinge angle or fold state, onHingeChange or UIHingeInteraction, interactions driven by folding, Split View multitasking or the stacked video layout on iPhone Duo, multiple scenes or windows on iPhone, requestSceneSessionActivation errors on the outer display, UIWindowSceneActivationAction, scene accessories, sceneAccessory, CameraCaptureAccessory, a teleprompter or subject preview on the outer display, the virtual front camera, AVCaptureDeviceDirectionCoordinator and cameras that change direction when the device opens or closes, preview mirroring, or a widget so the app appears in StandBy on the outer display. Based on Apple's "Leverage multiple displays and scenes" and "Build a great camera experience for iPhone Duo". Not for column widths, safe areas or content around the fold (layout) or toolbar items (bars).

1 other project uses a version of this, 1 of them edited.

Adopted by

Most recently taken up by

  • Other versions1 project uses a version of this.

    Word for word · 0

    None found.

    Wrote their own version · 0

    None found.

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository5 other files it gives its agents.

The file

We split this skill into the rules it gives an agent

12 rules. About 0 are common, 6 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    There is no pose value: No API says "book" or "tabletop".

    line 65
  2. Distinctive:

    No free-form drag-to-resize and no Stage Manager: scene size follows the pose and Split View, and no API sets it (Forums 847644).

    line 76
  3. Very specific:

    Verify: pose P8 in references/pose-test-matrix.md.

    line 83
  4. Very specific:

    New windows cannot be created on the outer display: Every scene request must handle failure (scanner rule DUO010 flags errorHandler: nil).

    line 95
  5. Very specific:

    Prefer UIWindowSceneActivationAction (Swift: UIWindowScene.ActivationAction) for "Open in New Window" controls: it hides itself when new …

    line 97
  6. Very specific:

    Create an AVCaptureDeviceDirectionCoordinator(view:deviceTypes:) on the preview view, listing every built-in camera the app captures from — …

    line 173
  7. Distinctive:

    Verify on a device — Simulator has no camera (pose P13).

    line 198
  8. Very specific:

    Verify poses P7, P8, P9, P10 and P13 in references/pose-test-matrix.md.

    line 224
  9. Distinctive:

    Never gate core functionality on the hinge or an accessory.

    line 231
  10. Distinctive:

    Don't read hinge state once and cache it.

    line 232
  11. Distinctive:

    Don't present a new-window affordance you have not checked is available.

    line 233
  12. Very specific:

    Don't infer which way a camera faces from its type or position.

    line 234

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

iPhone Duo displays, hinge, scenes and cameras

Code: references/displays-code.md. Availability: references/api-availability.md — the hinge APIs, CameraCaptureAccessory, the direction coordinator and the physical front camera types need the iOS 27.1 SDK, which ships with Xcode 27.1; sceneAccessory, onAvailabilityChange, UISceneAccessory and UIWindowScene.ActivationAction exist earlier. The hinge reaches you only through UIHingeInteraction / onHingeChange: there is no trait, no notification and no UIDevice property. StandBy cannot be verified in the iPhone Duo simulator (Xcode 27.1 known issues 187708663, 187708767). Which half carries the outer display and the inner camera, how each pose faces, and what each camera can do: references/device-geometry.md.

Hinge (Tech Talk 111464, 0:49–2:35)

  • SwiftUI onHingeChange { previous, context in … }; UIKit UIHingeInteraction { _, update in … } added to a view with addInteraction.
  • Reports a status — closed, partially open, fully open (UIKit also unknown) — and a continuous angle (UIHinge.angle is in radians; the SwiftUI sample uses Angle).
  • context.hinge == nil (UIKit: update.hinge == nil) means the device has no hinge or the view left a hierarchy that provides hinge updates: every hinge feature must degrade to nothing on other devices.
  • The action runs once with the initial state, then on every update and whenever the view moves between hierarchies (UIKit header, initWithUpdateHandler:). On that first call oldContext.hinge is nil (Booted, outer display: nil → closed at 0°), so set up from the new context instead of waiting for a change.
  • Pause with onHingeChange(isEnabled:) / UIHingeInteraction.isEnabled. Updates while disabled are dropped, not queued; re-enabling delivers the current state (UIKit header, isEnabled).
  • Prefer status when the posture is all you need. The rate and granularity of angle updates are system policy and change with system state (UIKit header, UIHinge.angle), and the status is the system's own reading of the angle and the device orientation (DeviceHinge documentation) — don't re-derive it from angle thresholds. DeviceHinge.Status is a struct with static members, not an enum, so a switch over it needs default (SDK). The simulator shows why (Booted, references/device-geometry.md): it reported partially open at 174°, fully open only at 180°, and closed already at 19°.
  • A call is not a change. On the simulator the action often ran with an old and a new context that were equal, several times per step. Compare before doing anything costly — a sound, a haptic, an animation (Booted).
  • Filter for .partiallyOpen when reading the angle, and reset in the else branch so the effect does not stick when the device opens flat or closes.
  • Hinge data drives interactions and effects, not layout. Layout uses arrangements and reserved regions (iphone-duo-layout).
  • Good fits: instruments, games, camera and media controls, playful physical interactions. Poor fits: anything essential — it must be reachable without folding.
  • There is no pose value. No API says "book" or "tabletop". Combine the hinge status and angle with orientation and size class when an effect needs the pose (Forums 847644); for layout, the shape of the active division region tells book from tabletop (iphone-duo-layout).

Split View multitasking (2:59)

  • All apps participate: two apps side by side on iPhone Duo, plus a new layout that stacks video and apps. There is no opt-out to lean on.
  • Apps that already resize on iPad or in iPhone Mirroring start in good shape. Handle it with size classes and scene geometry, and the asymmetric safe areas it produces.
  • No free-form drag-to-resize and no Stage Manager: scene size follows the pose and Split View, and no API sets it (Forums 847644).
  • Picture in Picture (Forums 847644): open in landscape, a person can pin PiP to the top while the app resizes beneath it; partially folded, the video takes half the screen and the app adjusts to the rest; PiP moves to the outer display only when the device closes, under system control. Expect the vertical size to change while video plays.
  • Verify: pose P8 in references/pose-test-matrix.md.

Multiple scenes (3:38)

  • iPhone Duo is the first iPhone that shows multiple instances of an app's UI; apps that support multiple scenes on iPad get this too.
  • Multiple scenes are not needed for folding. Opening, closing and folding resize the existing scene. Scenes matter for Split View with several instances and for scene accessories (Forums 847644).
  • Instances share app-wide state: UserDefaults and @AppStorage are not per window. Audio and video playback belongs to the instance that started playback most recently (Forums 847644).
  • New windows cannot be created on the outer display. Every scene request must handle failure (scanner rule DUO010 flags errorHandler: nil).
  • Prefer UIWindowSceneActivationAction (Swift: UIWindowScene.ActivationAction) for "Open in New Window" controls: it hides itself when new windows are not available.
  • Apps that enable UIApplicationSupportsMultipleScenes must not rely on global window state (iphone-duo-adaptivity-audit, rule DUO009).
  • An iPhone app that set the key to false to avoid coordinating state across windows now leaves Split View pairs and "open in new window" on the table. Treat it as a Tier 3 item: enable it only if the app tolerates several windows, test two windows on an iPad first, then on iPhone Duo. The scanner reports the key's value under project.supports_multiple_scenes.

Scene accessories (4:22–6:25)

  • Show content on more than one display at once, paired with the main UI — like a phone acting as a controller for a game on an external display.
  • The system controls availability and it can change at any time; accessories are enabled by default and can be toggled. Observe availability and update any control that turns the accessory on.
  • Camera capture accessory (camera apps): extra UI on the outer display while the main UI stays on the inner display — show the subject a preview, a countdown, a teleprompter. Available only when the app is full screen on the inner display with an active camera session. Register it on the same view as the camera UI, so it appears only while that view is visible.
  • Pattern: .sceneAccessory { CameraCaptureAccessory(isEnabled: $model.isEnabled) { … } .onAvailabilityChange { model.isAvailable = $0 } } and a toolbar toggle disabled when unavailable (for example when the device is closed).
  • UIKit: UISceneAccessory.cameraCapture(sceneConfiguration:userInfo:) passed to registerSceneAccessory(_:) on the capture view controller; keep the returned UISceneAccessoryRegistration (isAvailable is observable — read it in updateProperties(); set isEnabled from the toggle) and call unregisterSceneAccessory(_:) when the app stops offering the content. The accessory scene's session role is windowCameraCaptureAccessory; the shared model arrives as connectionOptions.sceneAccessoryUserInfo. No scene-manifest entry is needed or honored. (Apple documentation › Registering a camera capture accessory)
  • The system presents the top-most registration of a kind, withdraws content when capture stops, the app leaves the foreground or the device closes, and lets different accessory kinds coexist (a connected external display and the outer display at once). Share one model between the capture UI and the accessory instead of passing messages; the outer display accepts touch, so a single control (pause the script, tap to focus) is fine, a second interface is not.
  • Simulator has no camera: previews and Simulator check the accessory's layout, only a device shows it.
  • The system can withdraw accessory content at any moment, so its animations must start, pause and stop cleanly; drive timed animation with TimelineView or CADisplayLink (Forums 847644). An app cannot show an accessory without the conditions the system requires, only toggle its own content with isEnabled.
  • Outer display while open, without a camera: there is no general route. Apart from the camera capture accessory, AlarmKit alarms and a Live Activity are the way an alarm-clock or timer app reaches the outer display (Forums 847644).

Before proposing a camera accessory

An accessory is only available with an active camera session, so check the session really runs: a camera usage description (NSCameraUsageDescription), an authorization request, inputs added without swallowed errors, and outputs for what the app records. Missing prerequisites come first in the plan — otherwise the accessory silently never appears and looks like an iPhone Duo bug. Recording itself must never wait on the accessory being available.

Cameras (Tech Talk 111465; Apple documentation › Choosing a camera by the direction it faces)

iPhone Duo has two front cameras, both square ultrawide sensors: the outer camera and the under-display inner camera. Which way a camera faces depends on the display the app is on, and opening or closing the device moves the app to the other display.

  • Existing front-camera code keeps working. A DiscoverySession with position .front and the wide or ultra wide type returns the Virtual Front Camera, one AVCaptureDevice that streams from the inner camera when open and the outer camera when closed (isVirtualDevice == true; activePrimaryConstituent names the streaming camera). It offers only what both cameras share — 1080p, 60 fps, no depth. (1:01, 2:28)
  • Capture-first apps use the physical cameras. builtInOuterUltraWideCamera (up to 4K at 120 fps) and builtInInnerUltraWideCamera (1080p up to 60 fps) expose each camera fully, and switching on open/close becomes the app's job. (1:53)
  • position is where a camera sits, not where it points. Both front cameras report .front; a front camera can face away from the person looking at the app, and the rear cameras face the person when the open device is turned around. Create an AVCaptureDeviceDirectionCoordinator(view:deviceTypes:) on the preview view, listing every built-in camera the app captures from — rear types included, the two physical front types instead of the virtual one — and keep it alive while the view is on screen. Its handler reports an AVCaptureDeviceDirectionMap; pick from forwardFacingDeviceDescriptors when the active camera is no longer in it. (2:53–4:49)
  • The handler runs on the main actor and never touches AVFoundation. It hands an AVCaptureDeviceDescriptor (Sendable) to the actor that owns the session, which resolves it with AVCaptureDevice(uniqueID:), handles a nil result, and swaps the single video input inside beginConfiguration / commitConfiguration. Two displays showing a preview (a capture accessory) mean two coordinators, one per view. (5:37–6:16)
  • Mirror by direction, not position. A forward-facing rear camera is a selfie camera and needs a mirrored preview; a backward-facing front camera does not. Set automaticallyAdjustsVideoMirroring = false before assigning isVideoMirrored (assigning while it adjusts raises), only when position and direction disagree, and reapply after reconnecting an input. Mask the preview while the new camera starts.
  • Rotation. Adopt AVCaptureDevice.RotationCoordinator; it updates when the app moves displays, and it is created per device, so make a new one on every camera switch. Then disable camera-sensor-orientation compensation, which is on for every front camera on iPhone Duo, to save work. (8:08)
  • Preview polish. A full-field-of-view rear preview on the inner display leaves room around it: offset the preview and put controls in the remaining space, or fill with videoGravity. dynamicAspectRatio lets the square front sensors deliver a landscape frame on the inner display. (7:03)
  • Verify on a device — Simulator has no camera (pose P13).

StandBy on the outer display (Apple newsroom; HIG › Live Activities)

StandBy runs on either display "even when it's not charging": a folded or tented iPhone Duo on a desk is a screen facing the room, filled with widgets and Live Activities. An app with neither is absent from it. For apps that have a natural glanceable state (next event, timer, score, order status), a widget or Live Activity is a Tier 3 item; the scan inventory reports WidgetKit and Live Activities adoption. The Dynamic Island on the side stack expands vertically for Live Activities (HIG › Reserved regions), so nothing extra is needed there — but every presentation of the ActivityConfiguration must exist: compact leading and trailing, minimal, expanded, and the Lock Screen (Forums 847644). Alarm and timer apps adopt AlarmKit and let it present the alert (Forums 847644).

Workflow

  1. From the scan inventory: AVCaptureSession → camera accessory candidate, and with front camera discovery or video mirroring → camera direction review; multiple scene requests or UIApplicationSupportsMultipleScenes → scene handling review; custom gesture or sensor-driven features → hinge candidates; no WidgetKit / Live Activities → StandBy presence item.
  2. Correctness first: scene request errors, Split View sizes, cameras that face the wrong way after opening or closing. Then opportunities: accessory, hinge, widget.
  3. Recommend with references/recommendation-format.md; mark 27.1 APIs blocked when the SDK lacks them; apply after approval; build.
  4. Verify poses P7, P8, P9, P10 and P13 in references/pose-test-matrix.md. For hinge-driven effects, scripts/duo_pose.py set <degrees> (or hinge -d <udid> sweep) moves the simulator's hinge while the app runs — third-party hinge CLI; ask first. Expect repeated calls with equal contexts and a smoothed angle.

Guardrails

  • Never gate core functionality on the hinge or an accessory.
  • Don't read hinge state once and cache it; it is live.
  • Don't present a new-window affordance you have not checked is available.
  • Don't infer which way a camera faces from its type or position; ask a direction coordinator, and write one code path that also runs on single-display iPhones (there the coordinator reports once and never changes).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.