Skip to content

visionOS で iPad アプリ相当を動かす #11 - #51

Merged
temoki merged 3 commits into
mainfrom
feature/visionos
Aug 12, 2026
Merged

visionOS で iPad アプリ相当を動かす #11#51
temoki merged 3 commits into
mainfrom
feature/visionos

Conversation

@temoki

@temoki temoki commented Aug 12, 2026

Copy link
Copy Markdown
Owner

#11 の「visionOS で iPad アプリ相当が動く」まで。3 コミットに分けてあります。

1. Build and run on visionOS

SUPPORTED_PLATFORMSxros xrsimulatorXROS_DEPLOYMENT_TARGET 26.0TARGETED_DEVICE_FAMILY "2,7"UISupportsDocumentBrowser の visionOS 綴り(両ターゲット)。レイアウトは一切分岐していません — visionOS のシーンは regular width なので、同じ 3 ペインの NavigationSplitView がそのまま成立します。両パッケージは元から .visionOS(.v26) 宣言済みでした。

実際に手間だったのは #if で、両方向に問題がありました。

  • #if os(iOS) は visionOS で静かに効かなくなる — コンパイルは通るので気付けません。これで起動画面(DocumentGroupLaunchScene が unavailable なのは macOS だけ)・数字キーボード・44pt のタップ領域が丸ごと落ちていました。#if !os(macOS) に書き換え
  • 本当に無い API もあるToolbarSpacer(Liquid Glass のグルーピング区切り)。呼び出し側に #if を置かずに済ませるため、キャンバスのツールバーを CanvasToolbar: ToolbarContent に切り出しました

CI の app-build マトリクスに visionOS を追加したのはこのためです。「効かなくなった #if」を検出する手段は他にありません。

pointerHover()iOS 限定のままにしてあります。visionOS では視線フィードバックこそ本来の用途に見えますが、hoverEffect をパレットに付けるとウィンドウが出る前に落ちます(PaletteEntryButton.body を更新中の SwiftUI 内部の swift_release で segfault)。.automatic でも .highlight でも同じなので、効果の種類ではなくモディファイア自体です。理由はコードコメントに残しました。visionOS はボタンに標準のホバー処理を付けるので、見た目の実害はありません。

2. Open the workspace column at 440pt

これは visionOS 固有ではなく、3 プラットフォーム共通の変更なので分けました。

ideal の重みがプラットフォームで違います。macOS と iPadOS 26 はディバイダをドラッグできるので ideal は「開いたときの幅」ですが、visionOS はドラッグできないので ideal がその列の幅そのものになり、余った幅は全部 detail が吸います。1280pt のウィンドウで実測:

変更前 変更後
パレット 220pt 220pt
ワークスペース 360pt(= ideal 440pt
キャンバス 約 690pt(自分の ideal 420 を 270 超過) 約 600pt

360pt では 3 段ネストの日本語プログラムがラベルを折り返します。しかも深いところだけではなく、最上段の「くりかえす 10 かい」が か/い で割れていました(ネスト 0 段)。440 なら 3 段目の「はこにかける」まで全部 1 行に収まり、キャンバスも自分の ideal 420 を超えたままです。max を 440→560 にしたのは、ideal == max だとドラッグできる 2 つのプラットフォームが狭める方向にしか動かせなくなるためです。

3. Give visionOS an app icon

Icon Composer は squares(と watchOS の circles)しか書き出せないので、AppIcon.icon からは visionOS 用が 1 枚も出ず、ホーム画面はシステムのプレースホルダのままでした。Assets.xcassets/AppIcon.solidimagestack(Front / Middle / Back の 3 レイヤー、vision idiom の 1024×1024)を追加。

同名の 2 ソースは共存します。ASSETCATALOG_COMPILER_APPICON_NAME は全プラットフォーム共通で AppIcon のまま、actool が idiom で振り分けます。ビルド生成物で確認済み:

Assets.car
visionOS SolidImageStack | AppIcon + 3 レンディション、IconImageStack無し
iOS IconImageStack / IconGroup AppIcon/TortoiseSolidImageStack無し
macOS AppIcon.icns

確認したこと

  • 3 ビルド green(macOS / iOS Simulator / visionOS Simulator)、swift test 119 tests greenswift format lint --strict clean
  • visionOS 27 シミュレータで実アプリ・実書類を開いて目視 — パレット / ワークスペース / キャンバス / 再生行 / 書類タイトルが iPadOS と同じに出ること、ホーム画面でアイコンに円形マスクとレイヤーが掛かること
  • D&D と popover(4 つの値スロット)は @temoki 確認済み
  • macOS 1280×800 ウィンドウで列幅の変更を目視

未確認

  • iPad での列幅の見え方。シミュレータを横向きにできず(回転に入力送出が要る)、書類も openurl で開けなかったため。同じ ideal を読むので結果は同じはずですが、3 段ネストの日本語プログラムで見てもらえると確実です(英語だと収まってしまい、最上段の折り返しはフラットなプログラムでは出ません)
  • visionOS の実機。すべてシミュレータです

相談

**Fixes #11 を付けていません。**issue のチェックリスト 4 項目はすべて埋まりましたが、出荷側が残っているためです。

  • Xcode Cloud のワークフローに visionOS の archive を足すか(現在は iPadOS と macOS の 2 つだけ archive します)
  • App Store Connect 側の visionOS リスティング
  • site/index.htmlappstore/metadata/*/description.txt の「iPad and Mac」— この PR では意図的に触っていません。あれは「ダウンロードできるもの」の約束なので、出荷が決まってから直すのが正しいと判断しました。README だけは「このリポジトリから何がビルドできるか」なので visionOS 26+ を追加してあります

これらを別 issue に切って #11 を閉じるか、#11 に残すか、指示ください。閉じる方針なら最後のコミットに Fixes #11 を付け替えます。

temoki added 3 commits August 12, 2026 17:57
SUPPORTED_PLATFORMS gains xros/xrsimulator on both targets, with
XROS_DEPLOYMENT_TARGET 26.0, TARGETED_DEVICE_FAMILY "2,7" and the
visionOS spellings of UISupportsDocumentBrowser. Nothing in the layout
is conditional: the scene is regular width, so the same three-pane
NavigationSplitView fills the window, and both packages had already
declared .visionOS(.v26).

What the platform actually costs is the #ifs, in both directions. A
guard written `#if os(iOS)` compiles clean on visionOS and simply stops
applying, which had silently dropped the launch scene
(DocumentGroupLaunchScene is unavailable on macOS only), the numeric
keyboard and the 44pt touch targets; those are `#if !os(macOS)` now.
And some API is genuinely gone: ToolbarSpacer, the Liquid Glass
grouping separator, which is why the canvas toolbar becomes a
CanvasToolbar: ToolbarContent — somewhere for the #if to live that
isn't the call site. CI builds visionOS for exactly this reason;
nothing else catches a guard that quietly does nothing.

pointerHover() stays iOS-only, and says why. hoverEffect on a palette
entry segfaults inside SwiftUI's own update of PaletteEntryButton.body
before a window is ever shown, with .automatic as well as .highlight,
so it is the modifier rather than the effect. Buttons get the system's
hover treatment there anyway.

Verified by opening a real document in the visionOS 27 simulator:
palette, workspace, canvas, playback row and the document title all
render as they do on iPadOS, and drag & drop and the four value-slot
popovers behave.

The app icon is the one thing still missing. visionOS wants a circular
layered icon and Icon Composer only writes squares (plus watchOS
circles), so AppIcon.icon produces nothing for it and the system
placeholder is what shows on the Home View.

#11
`ideal` carries more weight than it looks. macOS and iPadOS 26 both let
the split view's divider be dragged, so there it is only where the
column opens; visionOS has no draggable divider, so there it is the
width, permanently, and the detail column absorbs every extra point the
window has. Measured on a 1280pt visionOS window: 360 for the workspace
against ~690 for the canvas, which is 270 past the canvas's own ideal.

At 360 a nested Japanese program wraps, and not only where the design
had accepted it. 「くりかえす 10 かい」 broke か/い at the *top* level, no
nesting involved, and 「はこにかける」 split in the middle three levels
down. 440 fits every row of that program on one line at three levels —
each level of nesting costs 18pt — and still leaves the canvas ~600pt.
max goes to 560 because ideal == max would leave the two platforms that
can drag able to drag only narrower.

Checked against the same three-deep document on visionOS 27 and on a
1280×800 Mac window. Judge any change to it on a nested *Japanese*
program: English fits where 「はこにかける」 does not, and the top-level
wrap is invisible in a flat one.

#11
Icon Composer writes squares (plus watchOS circles) and nothing else, so
AppIcon.icon left visionOS with the system placeholder. The circular
layered icon comes from a second source instead: an
AppIcon.solidimagestack of three .solidimagestacklayers — Front, Middle,
Back at 1024×1024 on the vision idiom — carrying the same artwork as the
Icon Composer icon, so the tortoise reads the same in the Home View as
it does in the Dock and on a Home Screen.

Both are named AppIcon and neither shadows the other:
ASSETCATALOG_COMPILER_APPICON_NAME is one value for every platform and
actool routes by idiom. Verified in the built products, not in Xcode —
the visionOS Assets.car holds a SolidImageStack and no IconImageStack,
the iOS one the reverse, and the Mac still ships AppIcon.icns. Checked
on the visionOS 27 Home View too, which is the only place the layers
and the circular mask are actually applied.

#11
@temoki
temoki merged commit d69283a into main Aug 12, 2026
7 checks passed
@temoki
temoki deleted the feature/visionos branch August 12, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant