diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47ba4a9..b168396 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,13 @@ jobs: destination: generic/platform=iOS Simulator - name: macOS destination: platform=macOS + # visionOS (#11). It shares every source file with iPadOS, so what + # this catches is the `#if` that quietly stops applying: SwiftUI + # marks a handful of iOS API unavailable there (`ToolbarSpacer`), + # and an `#if os(iOS)` compiles clean on all three platforms while + # doing nothing on this one. + - name: visionOS + destination: generic/platform=visionOS Simulator steps: - uses: actions/checkout@v7 diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/AppIcon_Back.png b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/AppIcon_Back.png new file mode 100644 index 0000000..ad0bfec Binary files /dev/null and b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/AppIcon_Back.png differ diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..fc68ee6 --- /dev/null +++ b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "AppIcon_Back.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Contents.json b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Contents.json new file mode 100644 index 0000000..950af4d --- /dev/null +++ b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.solidimagestacklayer" + }, + { + "filename" : "Middle.solidimagestacklayer" + }, + { + "filename" : "Back.solidimagestacklayer" + } + ] +} diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/AppIcon_Front.png b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/AppIcon_Front.png new file mode 100644 index 0000000..c70b7dc Binary files /dev/null and b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/AppIcon_Front.png differ diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..49293b9 --- /dev/null +++ b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "AppIcon_Front.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/AppIcon_Middle.png b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/AppIcon_Middle.png new file mode 100644 index 0000000..b58a0fb Binary files /dev/null and b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/AppIcon_Middle.png differ diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..a1e19d3 --- /dev/null +++ b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "AppIcon_Middle.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/App/Resources/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/TortoiseBlocksApp.swift b/App/TortoiseBlocksApp.swift index 012d593..c76a310 100644 --- a/App/TortoiseBlocksApp.swift +++ b/App/TortoiseBlocksApp.swift @@ -22,7 +22,7 @@ struct TortoiseBlocksApp: App { // unavailable on macOS and SwiftUI has no empty `Scene` to return in // its place, so this `#if` can't hide inside a modifier the way the // ones in `PlatformModifiers` do. - #if os(iOS) + #if !os(macOS) LaunchScene() #endif } diff --git a/App/Views/CLAUDE.md b/App/Views/CLAUDE.md index a24ea7c..02f3066 100644 --- a/App/Views/CLAUDE.md +++ b/App/Views/CLAUDE.md @@ -123,10 +123,24 @@ damage rather than fixing it: with the label served first, `0.6` in the next row broke as "0." over "6", which is worse — a chip holds a number, a name or a colour, all atomic. So `WorkspaceChipButtonStyle` pins its label with `.fixedSize(horizontal: true, vertical: false)`. Priority rather than -`fixedSize` on the *label*, deliberately: a row two levels deep with a long name -(「はこにかける」) genuinely runs out of room, and there the label should still -wrap instead of overflowing its block. Each level of nesting costs 18pt, so no -column width wins that race — the wrap is accepted there. +`fixedSize` on the *label*, deliberately: a row deep enough with a long name +genuinely runs out of room, and there the label should still wrap instead of +overflowing its block. Each level of nesting costs 18pt, so no column width wins +that race for ever — the wrap is the correct last resort, not the working state. + +**And `ideal` is not a starting point on every platform.** macOS and iPadOS 26 +both let the split view's divider be dragged, so there the ideal is only where +the workspace column *opens*. **visionOS has no draggable divider**, so there +the ideal is the width, for good, and the *detail* column absorbs every extra +point the window has. On a 1280pt visionOS window that used to read 360 here +and ~690 on the canvas (#11) — with 「くりかえす 10 かい」 wrapping か/い at the +**top** level, no nesting involved, and 「はこにかける」 splitting in the middle +three levels down. The ideal is now 440, measured against exactly that program: +every row fits on one line at three levels, and the canvas still clears its own +420 ideal. `max` (560) has to stay above `ideal`, or the two platforms that can +drag could only ever drag narrower. Judge a change to it on a *nested Japanese* +program — English fits where 「はこにかける」 does not, and the top-level wrap is +invisible in a flat one. **Drop model**: a `DropGap` between rows carries `(BodyAddress, index)`, so insertion semantics need no y-coordinate math and every mouth — an if's else diff --git a/App/Views/ContentView.swift b/App/Views/ContentView.swift index 1fcfc8b..86d8f4e 100644 --- a/App/Views/ContentView.swift +++ b/App/Views/ContentView.swift @@ -27,12 +27,13 @@ struct ContentView: View { } } -/// The one layout, on both platforms (#29). The app is iPad and Mac only, and -/// compact width — an iPhone, or an iPad window squeezed into Slide Over — is -/// no longer a design target: three panes' worth of information (palette, -/// program, canvas) folded into one 390pt column never came out usable. A -/// window narrow enough to go compact gets `NavigationSplitView`'s own -/// collapse, not a layout of ours. +/// The one layout, on every platform (#29) — iPad, Mac, and the visionOS +/// window, which is a regular-width scene and needs nothing of its own (#11). +/// Compact width — an iPhone, or an iPad window squeezed into Slide Over — is +/// not a design target: three panes' worth of information (palette, program, +/// canvas) folded into one 390pt column never came out usable. A window narrow +/// enough to go compact gets `NavigationSplitView`'s own collapse, not a layout +/// of ours. struct RootView: View { let workspace: WorkspaceEditor let runner: RunnerModel @@ -56,8 +57,21 @@ struct RootView: View { PaletteView(workspace: workspace) .navigationSplitViewColumnWidth(paletteWidth) } content: { + // 440pt is measured, not chosen, and `ideal` carries more weight + // than it looks. macOS and iPadOS both let the divider be dragged, + // so there the ideal is only where the column *opens*; on visionOS + // it can't be, so the ideal is the width, permanently, while the + // detail column absorbs every extra point — a 1280pt visionOS + // window put 360 here and ~690 on the canvas (#11). + // At 360 a three-deep program broke its labels onto two lines, and + // not only at depth: 「くりかえす 10 かい」 wrapped か/い at the top + // level, and 「はこにかける」 split in the middle. 440 fits every one + // of them on one line at three levels of nesting (each level costs + // 18pt), and still leaves the canvas ~600pt — well over its own 420 + // ideal. `max` has to stay above `ideal`, or the two platforms that + // can drag could only ever drag narrower. WorkspaceView(workspace: workspace, runner: runner) - .navigationSplitViewColumnWidth(min: 300, ideal: 360, max: 440) + .navigationSplitViewColumnWidth(min: 300, ideal: 440, max: 560) } detail: { // 280pt keeps the canvas usable (#23) — narrower and its own // playback row starts contesting space with the drawing. @@ -150,18 +164,8 @@ struct CanvasPane: View { // toolbar nor `navigationBarBackButtonHidden` touches it. .toolbar(removing: .title) .toolbar { - ToolbarSpacer(.flexible, placement: .primaryAction) - - ToolbarItemGroup(placement: .primaryAction) { - CanvasViewToggle(showsCode: $showsCode) - } - - ToolbarSpacer(.fixed, placement: .primaryAction) - - ToolbarItemGroup(placement: .primaryAction) { - CanvasRollAgainButton(workspace: workspace, runner: runner) - CanvasExportMenu(runner: runner, onExport: export) - } + CanvasToolbar( + workspace: workspace, runner: runner, showsCode: $showsCode, onExport: export) } // One alert, switching on why the run failed (`expansionAlert`): // attaching a second one for the recursion case would silently drop @@ -197,6 +201,39 @@ struct CanvasPane: View { } } +/// `CanvasPane`'s toolbar: the canvas/code toggle, then ⟳ and the export menu. +/// +/// It is a `ToolbarContent` type of its own only so the `#if` below has +/// somewhere to live that isn't the call site — `ToolbarSpacer` is the Liquid +/// Glass grouping separator and is unavailable on visionOS, which lays its +/// toolbar out as an ornament and spaces the groups itself. The items and +/// their order are the same everywhere; only the separators come and go. +struct CanvasToolbar: ToolbarContent { + let workspace: WorkspaceEditor + let runner: RunnerModel + @Binding var showsCode: Bool + let onExport: (Data?, UTType) -> Void + + var body: some ToolbarContent { + #if !os(visionOS) + ToolbarSpacer(.flexible, placement: .primaryAction) + #endif + + ToolbarItemGroup(placement: .primaryAction) { + CanvasViewToggle(showsCode: $showsCode) + } + + #if !os(visionOS) + ToolbarSpacer(.fixed, placement: .primaryAction) + #endif + + ToolbarItemGroup(placement: .primaryAction) { + CanvasRollAgainButton(workspace: workspace, runner: runner) + CanvasExportMenu(runner: runner, onExport: onExport) + } + } +} + /// The canvas/code segmented toggle, in `CanvasPane`'s toolbar (#23). struct CanvasViewToggle: View { @Binding var showsCode: Bool diff --git a/App/Views/LaunchScene.swift b/App/Views/LaunchScene.swift index 6031154..a7dcc0e 100644 --- a/App/Views/LaunchScene.swift +++ b/App/Views/LaunchScene.swift @@ -1,11 +1,12 @@ -#if os(iOS) +#if !os(macOS) import SwiftUI /// The screen in front of the system document browser (#32). /// - /// `DocumentGroupLaunchScene` is `@available(macOS, unavailable)`, so this - /// whole file is iOS-only and the Mac keeps the standard open panel. + /// `DocumentGroupLaunchScene` is `@available(macOS, unavailable)` and + /// nothing else, so this file covers iPadOS and visionOS while the Mac + /// keeps the standard open panel. struct LaunchScene: Scene { var body: some Scene { DocumentGroupLaunchScene( diff --git a/App/Views/PlatformModifiers.swift b/App/Views/PlatformModifiers.swift index 7bd8fe1..724f4a5 100644 --- a/App/Views/PlatformModifiers.swift +++ b/App/Views/PlatformModifiers.swift @@ -2,13 +2,20 @@ import SwiftUI // Small cross-cutting modifiers that hide their `#if os(...)` inside a // modifier (the SwiftUI-way rule), so call sites stay platform-agnostic. +// +// They are written `#if !os(macOS)` rather than naming iOS wherever the +// behaviour is simply "the touch platforms": these are UIKit-backed and exist +// on visionOS as well as iPadOS (#11), and spelling the condition as "not the +// Mac" is what keeps a new platform from silently taking the no-op branch the +// way visionOS did — an `#if os(iOS)` compiles clean everywhere and just stops +// applying. `pointerHover` is the exception, and says why. extension View { - /// The number pad for numeric entry on iOS (#24); a no-op elsewhere. - /// `.decimalPad` gives the digits and decimal point kids need; the - /// number blocks don't take negative literals, so its lack of a minus - /// key is intentional. + /// The number pad for numeric entry (#24); a no-op on macOS, which has a + /// hardware keyboard. `.decimalPad` gives the digits and decimal point kids + /// need; the number blocks don't take negative literals, so its lack of a + /// minus key is intentional. func numericKeyboard() -> some View { - #if os(iOS) + #if !os(macOS) keyboardType(.decimalPad) #else self @@ -17,6 +24,16 @@ extension View { /// The iPad (pointer) hover highlight (#24); a no-op on macOS, which has /// its own cursor affordances. + /// + /// Deliberately *not* extended to visionOS, where gaze feedback would seem + /// to be exactly what this is for: `hoverEffect` on a palette entry crashes + /// the app there. Launching straight into the workspace segfaults in + /// `PaletteEntryButton.body` — a `swift_release` inside SwiftUI's own + /// update, not our code — before a window is ever shown, and it does so + /// with `.automatic` as well as `.highlight`, so it is the modifier and not + /// the effect. (visionOS 26.5 / 27.0 simulators, Xcode 26.6.) Buttons get + /// the system's own hover treatment there in any case; this is only the + /// extra highlight iPadOS needs, so the platform loses nothing visible. func pointerHover() -> some View { #if os(iOS) hoverEffect(.highlight) @@ -25,7 +42,9 @@ extension View { #endif } - /// Holds an icon-only control to the 44pt finger minimum on iPadOS. + /// Holds an icon-only control to the 44pt finger minimum on iPadOS (and to + /// the same floor on visionOS, where the target is a gaze rather than a + /// finger — 44 is the iPad number, not a measured visionOS one). /// A borderless SF Symbol button is only as tappable as the glyph is big — /// around 24pt at body size — so the ⋯ on a block row is a small target on /// a touch screen even though the row around it is not. @@ -44,7 +63,7 @@ extension View { /// this is used sits after a `Spacer`, so the extra width takes slack /// instead of pushing the label. func touchTarget() -> some View { - #if os(iOS) + #if !os(macOS) frame(minWidth: 44, minHeight: 44).contentShape(.rect) #else self diff --git a/CLAUDE.md b/CLAUDE.md index f395117..e79cefd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -303,7 +303,8 @@ And a macOS QuickLook extension has to be sandboxed to be loaded, so it carries even though the app itself has none. `pluginkit -mAvvv | grep -i tortoise` confirms registration — the `-p com.apple.quicklook.thumbnail` filter does not match it and will make a working extension look missing. -`TARGETED_DEVICE_FAMILY` is `"2"` — iPad and Mac, no iPhone (#29). +`TARGETED_DEVICE_FAMILY` is `"2,7"` — iPad, Mac and Vision Pro, no iPhone +(#29, #11). Documents are `.tortoise` files, but the exported UTI keeps the `tortoiseblocks` spelling (`space.hiraku.tortoiseblocks.project`, and `.block` for the drag payload), which is also the bundle ID's — the @@ -370,6 +371,44 @@ rules apply. The app takes `files.user-selected.read-write` for the `read-only` and is sandboxed for a different reason (a macOS QuickLook extension is not loaded otherwise). +**visionOS runs the iPad app, not a port** (#11). `SUPPORTED_PLATFORMS` gains +`xros xrsimulator`, `XROS_DEPLOYMENT_TARGET` is 26.0, and the same three-pane +`NavigationSplitView` fills the window — the scene is regular width, so nothing +in the layout is platform-conditional and no ornament or volumetric anything is +declared. Both packages already shipped `.visionOS(.v26)`. What the platform +actually costs is the `#if`s, in both directions. **A guard written +`#if os(iOS)` stops applying** — it still compiles everywhere, so `LaunchScene` +(`DocumentGroupLaunchScene` is unavailable on *macOS* only) and the numeric +keyboard and touch targets in `PlatformModifiers` were silently dropped until +they were rewritten as `#if !os(macOS)`. And some UIKit-era API is genuinely +gone: `ToolbarSpacer` — the Liquid Glass grouping separator — is unavailable, +which is the only reason `CanvasToolbar` exists as a `ToolbarContent` type of +its own. That is also why CI builds visionOS: nothing else catches an `#if` +that quietly does nothing. +One layout constant *is* load-bearing here, though the layout itself isn't +conditional: visionOS has **no draggable split-view divider** — macOS and +iPadOS 26 both do — so the workspace column is stuck at its `ideal` for good +while the canvas takes the rest of a wide window. That is what set the ideal at +440; the measurement is in `App/Views/CLAUDE.md`. +One thing is known-missing rather than done: **`hoverEffect` crashes** there — +`.automatic` as well as `.highlight`, a `swift_release` segfault inside +SwiftUI's update of `PaletteEntryButton.body`, before a window appears — so +`pointerHover()` stays iOS-only and says so. + +**The app icon comes from a second, differently-shaped source.** visionOS wants +a circular layered icon and Icon Composer writes only squares (plus watchOS +circles), so `AppIcon.icon` produces nothing for it — +`Assets.xcassets/AppIcon.solidimagestack` does, three +`.solidimagestacklayer`s (Front / Middle / Back) of 1024×1024 at the `vision` +idiom. The two carry the same name on purpose and do **not** collide: +`ASSETCATALOG_COMPILER_APPICON_NAME` is `AppIcon` for every platform and actool +routes by idiom, so the visionOS `Assets.car` gets a `SolidImageStack` and no +`IconImageStack`, iOS gets the reverse, and macOS still gets `AppIcon.icns`. +Check a change here in the *built* product rather than in Xcode — `assetutil +--info` on each platform's `Assets.car` — because a stack that never made it in +fails the same silent way a missing one does: the system's placeholder, which +looks like a plain app that hasn't been styled yet. + **Releasing, the store listing and the website are in the `release` skill.** Tags, Xcode Cloud, TestFlight, `appstore/`, fastlane, and `site/`. **Localization**: `en` is the source language; Japanese (kid-friendly diff --git a/README.md b/README.md index da35bb0..efdafda 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Swift](https://img.shields.io/badge/Swift-6.2-orange.svg)](https://swift.org) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Platform](https://img.shields.io/badge/platform-iPadOS%2026%2B%20%7C%20macOS%2026%2B-lightgrey.svg)]() +[![Platform](https://img.shields.io/badge/platform-iPadOS%2026%2B%20%7C%20macOS%2026%2B%20%7C%20visionOS%2026%2B-lightgrey.svg)]() A visual programming app for kids — snap blocks together, press play, and watch the tortoise draw. Powered by @@ -56,7 +56,8 @@ graphics engine written in Swift. ## Requirements - **Xcode** 26+ (Swift 6.2) -- **Platforms** iPadOS 26+ · macOS 26+ (visionOS planned) +- **Platforms** iPadOS 26+ · macOS 26+ · visionOS 26+ (the same three-pane app + in a window; not on the App Store yet) ## Getting Started @@ -171,11 +172,11 @@ arithmetic saturates the same way, so a value can never run off to infinity. ## Releasing A `v*` tag is the release. Pushing one starts an Xcode Cloud workflow that -archives both platforms and sends them to TestFlight, while GitHub Actions -checks that the tag matches `MARKETING_VERSION` in every configuration — the -two are otherwise unconnected, and a mismatch would ship the wrong version -silently. The same tag drafts a GitHub release, with notes split by whether a -commit reached the app or only the site, the listing, CI or the docs. +archives the iPadOS and macOS apps and sends them to TestFlight, while GitHub +Actions checks that the tag matches `MARKETING_VERSION` in every configuration +— the two are otherwise unconnected, and a mismatch would ship the wrong +version silently. The same tag drafts a GitHub release, with notes split by +whether a commit reached the app or only the site, the listing, CI or the docs. The store listing is not part of that. It lives in [appstore/](appstore/) and goes up on demand, by hand: diff --git a/TortoiseBlocks.xcodeproj/project.pbxproj b/TortoiseBlocks.xcodeproj/project.pbxproj index ae216f2..8f4e594 100644 --- a/TortoiseBlocks.xcodeproj/project.pbxproj +++ b/TortoiseBlocks.xcodeproj/project.pbxproj @@ -394,6 +394,8 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=xros*]" = YES; + "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=xrsimulator*]" = YES; IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; @@ -402,9 +404,10 @@ PRODUCT_BUNDLE_IDENTIFIER = space.hiraku.tortoiseblocks; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "2,7"; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Debug; }; @@ -429,6 +432,8 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=xros*]" = YES; + "INFOPLIST_KEY_UISupportsDocumentBrowser[sdk=xrsimulator*]" = YES; IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; @@ -437,9 +442,10 @@ PRODUCT_BUNDLE_IDENTIFIER = space.hiraku.tortoiseblocks; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "2,7"; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Release; }; @@ -470,9 +476,10 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "2,7"; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Debug; }; @@ -501,9 +508,10 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "2,7"; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Release; };