From 0d92b3399745b020cca7461e14b78328e8e6d508 Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Mon, 2 Mar 2026 19:14:39 +0100 Subject: [PATCH] Add Apple Vision Pro (M2) and Apple Vision Pro (M5) support Implement full visionOS device support with both Vision Pro models (identifiers RealityDevice14,1 and RealityDevice17,1). Replace all visionOS TODO placeholders with proper implementations including device enum cases, identifier mapping, property getters (description, safeDescription, cpu), and static device arrays. Add comprehensive unit tests for visionOS platform. Enable visionOS simulator testing in GitHub Actions CI pipeline. Co-Authored-By: Claude Opus 4.6 --- .context/notes.md | 0 .context/todos.md | 0 .github/workflows/main.yml | 1 + CHANGELOG.md | 3 ++ Source/Device.generated.swift | 62 ++++++++++++++++++++++-------- Source/Device.swift.gyb | 71 +++++++++++++++++++++++++++-------- Tests/Tests.swift | 26 +++++++++++++ 7 files changed, 131 insertions(+), 32 deletions(-) create mode 100644 .context/notes.md create mode 100644 .context/todos.md diff --git a/.context/notes.md b/.context/notes.md new file mode 100644 index 0000000..e69de29 diff --git a/.context/todos.md b/.context/todos.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 55e1087..00081be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,6 +43,7 @@ jobs: - platform=tvOS Simulator,name=Apple TV - platform=tvOS Simulator,name=Apple TV 4K (3rd generation) - platform=watchOS Simulator,name=Apple Watch Series 10 (46mm) + - platform=visionOS Simulator,name=Apple Vision Pro steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3248e56..11b3c4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add support for M5 iPad Pro models. ([#467](https://github.com/devicekit/DeviceKit/pull/467)) - Add support for Apple Watch SE (3rd generation) ([#473](https://github.com/devicekit/DeviceKit/pull/473)) - Add support for iPhone 17e and iPad Air (M4) +- Add support for Apple Vision Pro and Apple Vision Pro (M5) | Device | Case value | | --- | --- | @@ -17,6 +18,8 @@ | iPhone 17e | `Device.iPhone17e` | | iPad Air (11-inch) (M4) | `Device.iPadAir11M4` | | iPad Air (13-inch) (M4) | `Device.iPadAir13M4` | +| Apple Vision Pro | `Device.appleVisionPro` | +| Apple Vision Pro (M5) | `Device.appleVisionProM5` | ### Bug fixes diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index d67b11d..6209ca7 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -588,6 +588,15 @@ public enum Device { /// /// ![Image]() case appleWatchSeries11_46mm + #elseif os(visionOS) + /// Device is an [Apple Vision Pro](https://support.apple.com/kb/SP911) + /// + /// ![Image]() + case appleVisionPro + /// Device is an [Apple Vision Pro (M5)](https://support.apple.com/en-us/125436) + /// + /// ![Image]() + case appleVisionProM5 #endif /// Device is [Simulator](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/iOS_Simulator_Guide/Introduction/Introduction.html) @@ -774,8 +783,12 @@ public enum Device { default: return unknown(identifier) } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return unknown(identifier) + switch identifier { + case "RealityDevice14,1": return appleVisionPro + case "RealityDevice17,1": return appleVisionProM5 + case "i386", "x86_64", "arm64": return simulator(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "visionOS")) + default: return unknown(identifier) + } #else return unknown(identifier) #endif @@ -1344,6 +1357,16 @@ public enum Device { public var hasForceTouchSupport: Bool { return isOneOf(Device.allWatchesWithForceTouchSupport) || isOneOf(Device.allWatchesWithForceTouchSupport.map(Device.simulator)) } + #elseif os(visionOS) + /// All Vision devices + public static var allVisions: [Device] { + return [.appleVisionPro, .appleVisionProM5] + } + + /// All simulator Vision devices + public static var allSimulatorVisions: [Device] { + return allVisions.map(Device.simulator) + } #endif /// Returns whether the current device is a SwiftUI preview canvas @@ -1365,8 +1388,7 @@ public enum Device { #elseif os(watchOS) return allWatches #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return [] + return allVisions #else return [] #endif @@ -1636,7 +1658,6 @@ public enum Device { #elseif os(tvOS) return nil #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. return nil #else return nil @@ -1821,8 +1842,12 @@ extension Device: CustomStringConvertible { case .unknown(let identifier): return identifier } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return "Apple Vision Pro" + switch self { + case .appleVisionPro: return "Apple Vision Pro" + case .appleVisionProM5: return "Apple Vision Pro (M5)" + case .simulator(let model): return "Simulator (\(model.description))" + case .unknown(let identifier): return identifier + } #else switch self { case .simulator(let model): return "Simulator (\(model.description))" @@ -1986,8 +2011,12 @@ extension Device: CustomStringConvertible { case .unknown(let identifier): return identifier } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return "Apple Vision Pro" + switch self { + case .appleVisionPro: return "Apple Vision Pro" + case .appleVisionProM5: return "Apple Vision Pro (M5)" + case .simulator(let model): return "Simulator (\(model.safeDescription))" + case .unknown(let identifier): return identifier + } #else switch self { case .simulator(let model): return "Simulator (\(model.safeDescription))" @@ -2508,7 +2537,7 @@ extension Device { extension Device { public enum CPU: Comparable { - #if os(iOS) || os(tvOS) + #if os(iOS) || os(tvOS) || os(visionOS) case a4 case a5 case a5X @@ -2707,8 +2736,12 @@ extension Device { case .unknown: return .unknown } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return .unknown + switch self { + case .appleVisionPro: return .m2 + case .appleVisionProM5: return .m5 + case .simulator(let model): return model.cpu + case .unknown: return .unknown + } #else return .unknown #endif @@ -2719,7 +2752,7 @@ extension Device.CPU: CustomStringConvertible { /// A textual representation of the device. public var description: String { - #if os(iOS) || os(tvOS) + #if os(iOS) || os(tvOS) || os(visionOS) switch self { case .a4: return "A4" case .a5: return "A5" @@ -2768,9 +2801,6 @@ extension Device.CPU: CustomStringConvertible { case .s10: return "S10" case .unknown: return "unknown" } - #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return "unknown" #else return "unknown" #endif diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index 11a203e..97b6c90 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -156,6 +156,12 @@ tvs = [ Device("appleTV4K3", "Device is an [Apple TV 4K (3rd generation)](https://support.apple.com/kb/SP886)", "https://support.apple.com/library/APPLE/APPLECARE_ALLGEOS/SP886/apple-tv-4k-3gen_2x.png", ["AppleTV14,1"], 0, (), "Apple TV 4K (3rd generation)", "Apple TV 4K (3rd generation)", -1, False, False, False, False, False, False, False, False, False, False, 0, False, 0, False, "a15Bionic", False, False), ] +# visionOS +visions = [ + Device("appleVisionPro", "Device is an [Apple Vision Pro](https://support.apple.com/kb/SP911)", "", ["RealityDevice14,1"], 0, (1, 1), "Apple Vision Pro", "Apple Vision Pro", -1, False, False, False, False, False, False, False, False, False, False, 0, False, 0, True, "m2", True, False), + Device("appleVisionProM5", "Device is an [Apple Vision Pro (M5)](https://support.apple.com/en-us/125436)", "", ["RealityDevice17,1"], 0, (1, 1), "Apple Vision Pro (M5)", "Apple Vision Pro (M5)", -1, False, False, False, False, False, False, False, False, False, False, 0, False, 0, True, "m5", True, False), + ] + # watchOS watches = [ Device( @@ -361,6 +367,7 @@ watches = [ iOSDevices = iPods + iPhones + iPads + homePods tvOSDevices = tvs watchOSDevices = watches +visionOSDevices = visions }% #if os(watchOS) import WatchKit @@ -422,6 +429,13 @@ public enum Device { /// /// ![Image](${device.imageURL}) case ${device.caseName} +% end + #elseif os(visionOS) +% for device in visionOSDevices: + /// ${device.comment} + /// + /// ![Image](${device.imageURL}) + case ${device.caseName} % end #endif @@ -484,8 +498,13 @@ public enum Device { default: return unknown(identifier) } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return unknown(identifier) + switch identifier { +% for device in visionOSDevices: + case ${', '.join(list(map(lambda device: "\"" + device + "\"", device.identifiers)))}: return ${device.caseName} +% end + case "i386", "x86_64", "arm64": return simulator(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "visionOS")) + default: return unknown(identifier) + } #else return unknown(identifier) #endif @@ -806,6 +825,16 @@ public enum Device { public var hasForceTouchSupport: Bool { return isOneOf(Device.allWatchesWithForceTouchSupport) || isOneOf(Device.allWatchesWithForceTouchSupport.map(Device.simulator)) } + #elseif os(visionOS) + /// All Vision devices + public static var allVisions: [Device] { + return [${', '.join(list(map(lambda device: "." + device.caseName, visionOSDevices)))}] + } + + /// All simulator Vision devices + public static var allSimulatorVisions: [Device] { + return allVisions.map(Device.simulator) + } #endif /// Returns whether the current device is a SwiftUI preview canvas @@ -827,8 +856,7 @@ public enum Device { #elseif os(watchOS) return allWatches #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return [] + return allVisions #else return [] #endif @@ -974,7 +1002,6 @@ public enum Device { #elseif os(tvOS) return nil #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. return nil #else return nil @@ -1034,8 +1061,13 @@ extension Device: CustomStringConvertible { case .unknown(let identifier): return identifier } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return "Apple Vision Pro" + switch self { +% for device in visionOSDevices: + case .${device.caseName}: return "${device.description}" +% end + case .simulator(let model): return "Simulator (\(model.description))" + case .unknown(let identifier): return identifier + } #else switch self { case .simulator(let model): return "Simulator (\(model.description))" @@ -1074,8 +1106,13 @@ extension Device: CustomStringConvertible { case .unknown(let identifier): return identifier } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return "Apple Vision Pro" + switch self { + % for device in visionOSDevices: + case .${device.caseName}: return "${device.safeDescription}" + % end + case .simulator(let model): return "Simulator (\(model.safeDescription))" + case .unknown(let identifier): return identifier + } #else switch self { case .simulator(let model): return "Simulator (\(model.safeDescription))" @@ -1568,7 +1605,7 @@ watchOS_cpus = [ extension Device { public enum CPU: Comparable { - #if os(iOS) || os(tvOS) + #if os(iOS) || os(tvOS) || os(visionOS) % for cpu in iOS_cpus: case ${cpu.name} % end @@ -1607,8 +1644,13 @@ extension Device { case .unknown: return .unknown } #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return .unknown + switch self { + % for device in visionOSDevices: + case .${device.caseName}: return .${device.cpu} + % end + case .simulator(let model): return model.cpu + case .unknown: return .unknown + } #else return .unknown #endif @@ -1619,7 +1661,7 @@ extension Device.CPU: CustomStringConvertible { /// A textual representation of the device. public var description: String { - #if os(iOS) || os(tvOS) + #if os(iOS) || os(tvOS) || os(visionOS) switch self { % for cpu in iOS_cpus: case .${cpu.name}: return "${cpu.description}" @@ -1633,9 +1675,6 @@ extension Device.CPU: CustomStringConvertible { % end case .unknown: return "unknown" } - #elseif os(visionOS) - // TODO: Replace with proper implementation for visionOS. - return "unknown" #else return "unknown" #endif diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 1e87af8..ebc5199 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -819,4 +819,30 @@ class DeviceKitTests: XCTestCase { #endif + // MARK: - visionOS + #if os(visionOS) + + func testMapFromIdentifier() { + XCTAssertEqual(Device.mapToDevice(identifier: "RealityDevice14,1"), .appleVisionPro) + XCTAssertEqual(Device.mapToDevice(identifier: "RealityDevice17,1"), .appleVisionProM5) + } + + func testDeviceCPU() { + XCTAssertEqual(Device.appleVisionPro.cpu, Device.CPU.m2) + XCTAssertEqual(Device.appleVisionProM5.cpu, Device.CPU.m5) + } + + func testDescription() { + XCTAssertEqual(Device.appleVisionPro.description, "Apple Vision Pro") + XCTAssertEqual(Device.appleVisionProM5.description, "Apple Vision Pro (M5)") + } + + func testSafeDescription() { + for device in Device.allRealDevices { + XCTAssertEqual(device.description, device.safeDescription) + } + } + + #endif + }