From 206ea7a0b8b10f5d4433a830bb2cbd76ef043cfc Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Wed, 9 Sep 2026 20:26:08 +0200 Subject: [PATCH 01/11] Add provisional iPhone Duo support --- CHANGELOG.md | 5 +++++ Source/Device.generated.swift | 24 ++++++++++++++++++++++- Source/Device.swift.gyb | 23 ++++++++++++++++++++++ Tests/Tests.swift | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d8410d..e02745e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Provisional iPhone Duo support + +- Add `Device.iPhoneDuo` with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. +- Use `placeholder:iPhoneDuo` until the hardware identifier is confirmed, and omit the image. Display metrics remain unknown (`-1` for diagonal/ratio, `nil` for PPI); other unconfirmed capabilities use false/empty defaults. Automatic hardware detection requires a confirmed identifier. + ## Version 5.9.0 Releasedate: 2026-09-22 diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index 1fc55345..bf279dd7 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -236,6 +236,8 @@ public enum Device { case iPhone18Pro /// Device is an [iPhone 18 Pro Max](https://support.apple.com/en-us/148591) case iPhone18ProMax + /// Provisional iPhone Duo; identifier, display metrics, and other capabilities await verification. + case iPhoneDuo /// Device is an [iPad 2](https://support.apple.com/kb/SP622) /// /// ![Image](https://support.apple.com/library/APPLE/APPLECARE_ALLGEOS/SP622/SP622_01-ipad2-mul.png) @@ -638,6 +640,7 @@ public enum Device { case "iPhone18,5": return iPhone17e case "iPhone19,2": return iPhone18Pro case "iPhone19,3", "iPhone19,7": return iPhone18ProMax + case "placeholder:iPhoneDuo": return iPhoneDuo case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return iPad2 case "iPad3,1", "iPad3,2", "iPad3,3": return iPad3 case "iPad3,4", "iPad3,5", "iPad3,6": return iPad4 @@ -744,6 +747,19 @@ public enum Device { #endif } + /// Returns whether the device has a foldable display, including simulators. + public var isFoldable: Bool { + #if os(iOS) + switch self { + case .iPhoneDuo: return true + case .simulator(let model): return model.isFoldable + default: return false + } + #else + return false + #endif + } + /// Get the real device from a device. /// If the device is a an iPhone8Plus simulator this function returns .iPhone8Plus (the real device). /// If the parameter is a real device, this function returns just that passed parameter. @@ -818,6 +834,7 @@ public enum Device { case .iPhone17e: return 6.1 case .iPhone18Pro: return 6.3 case .iPhone18ProMax: return 6.9 + case .iPhoneDuo: return -1 case .iPad2: return 9.7 case .iPad3: return 9.7 case .iPad4: return 9.7 @@ -969,6 +986,7 @@ public enum Device { case .iPhone17e: return (width: 9, height: 19.5) case .iPhone18Pro: return (width: 9, height: 19.5) case .iPhone18ProMax: return (width: 9, height: 19.5) + case .iPhoneDuo: return (width: -1, height: -1) case .iPad2: return (width: 3, height: 4) case .iPad3: return (width: 3, height: 4) case .iPad4: return (width: 3, height: 4) @@ -1073,7 +1091,7 @@ public enum Device { /// All iPhones public static var allPhones: [Device] { - return [.iPhone4, .iPhone4s, .iPhone5, .iPhone5c, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax] + return [.iPhone4, .iPhone4s, .iPhone5, .iPhone5c, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo] } /// All iPads @@ -1551,6 +1569,7 @@ public enum Device { case .iPhone17e: return 460 case .iPhone18Pro: return 460 case .iPhone18ProMax: return 460 + case .iPhoneDuo: return nil case .iPad2: return 132 case .iPad3: return 264 case .iPad4: return 264 @@ -1734,6 +1753,7 @@ extension Device: CustomStringConvertible { case .iPhone17e: return "iPhone 17e" case .iPhone18Pro: return "iPhone 18 Pro" case .iPhone18ProMax: return "iPhone 18 Pro Max" + case .iPhoneDuo: return "iPhone Duo" case .iPad2: return "iPad 2" case .iPad3: return "iPad (3rd generation)" case .iPad4: return "iPad (4th generation)" @@ -1904,6 +1924,7 @@ extension Device: CustomStringConvertible { case .iPhone17e: return "iPhone 17e" case .iPhone18Pro: return "iPhone 18 Pro" case .iPhone18ProMax: return "iPhone 18 Pro Max" + case .iPhoneDuo: return "iPhone Duo" case .iPad2: return "iPad 2" case .iPad3: return "iPad (3rd generation)" case .iPad4: return "iPad (4th generation)" @@ -2634,6 +2655,7 @@ extension Device { case .iPhone17e: return .a19 case .iPhone18Pro: return .a20Pro case .iPhone18ProMax: return .a20Pro + case .iPhoneDuo: return .a20Pro case .iPad2: return .a5 case .iPad3: return .a5X case .iPad4: return .a6X diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index 63b4348a..b7aa32b0 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -32,6 +32,7 @@ class Device: has5gSupport=False, hasEsimSupport=False, hasDualEsimSupport=False, + isFoldable=False, ): self.caseName = caseName self.comment = comment @@ -61,6 +62,7 @@ class Device: self.has5gSupport = has5gSupport self.hasEsimSupport = hasEsimSupport self.hasDualEsimSupport = hasDualEsimSupport + self.isFoldable = isFoldable # iOS iPods = [ @@ -284,6 +286,10 @@ iPhones = [ hasDynamicIsland=True, cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True, hasEsimSupport=True, hasDualEsimSupport=True), + + # Duo capabilities other than its chip and foldability are unconfirmed (false/empty defaults). + Device("iPhoneDuo", "Provisional iPhone Duo; identifier, display metrics, and other capabilities await verification.", "", ["placeholder:iPhoneDuo"], -1, (-1, -1), "iPhone Duo", "iPhone Duo", "nil", + cpu="a20Pro", isFoldable=True), ] iPads = [ @@ -828,6 +834,23 @@ public enum Device { #endif } + /// Returns whether the device has a foldable display, including simulators. + public var isFoldable: Bool { + #if os(iOS) + switch self { +% for device in iOSDevices: +% if device.isFoldable: + case .${device.caseName}: return true +% end +% end + case .simulator(let model): return model.isFoldable + default: return false + } + #else + return false + #endif + } + /// Get the real device from a device. /// If the device is a an iPhone8Plus simulator this function returns .iPhone8Plus (the real device). /// If the parameter is a real device, this function returns just that passed parameter. diff --git a/Tests/Tests.swift b/Tests/Tests.swift index ce99bbc6..936bbad7 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -16,6 +16,42 @@ class DeviceKitTests: XCTestCase { let device = Device.current + func testIsFoldable() { + for model in Device.allRealDevices { + #if os(iOS) + let expected = model == .iPhoneDuo + #else + let expected = false + #endif + XCTAssertEqual(model.isFoldable, expected, model.description) + XCTAssertEqual(Device.simulator(model).isFoldable, expected, model.description) + } + XCTAssertFalse(Device.unknown("unrecognized").isFoldable) + XCTAssertFalse(Device.simulator(.unknown("unrecognized")).isFoldable) + } + + #if os(iOS) + func testProvisionalIPhoneDuo() { + let model = Device.iPhoneDuo + XCTAssertEqual(Device.mapToDevice(identifier: "placeholder:iPhoneDuo"), model) + XCTAssertEqual(model.description, "iPhone Duo") + XCTAssertEqual(model.safeDescription, "iPhone Duo") + XCTAssertTrue(Device.allPhones.contains(model)) + XCTAssertTrue(Device.allRealDevices.contains(model)) + XCTAssertTrue(Device.allSimulators.contains(.simulator(model))) + for variant in [model, .simulator(model)] { + XCTAssertTrue(variant.isPhone) + XCTAssertFalse(variant.isPad) + XCTAssertTrue(variant.isFoldable) + XCTAssertEqual(variant.cpu, .a20Pro) + XCTAssertEqual(variant.diagonal, -1) + XCTAssertEqual(variant.screenRatio.width, -1) + XCTAssertEqual(variant.screenRatio.height, -1) + XCTAssertNil(variant.ppi) + } + } + #endif + #if os(iOS) || os(watchOS) func testSeptember2026Devices() { struct ExpectedDevice { From cd5fb71afb80f57bd4827e69c8f4f4292307fc83 Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 22 Sep 2026 19:46:28 +0200 Subject: [PATCH 02/11] Use the iPhone Duo hardware identifier --- CHANGELOG.md | 2 +- Source/Device.generated.swift | 4 ++-- Source/Device.swift.gyb | 2 +- Tests/Tests.swift | 7 ++++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e02745e7..650e5544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Provisional iPhone Duo support - Add `Device.iPhoneDuo` with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. -- Use `placeholder:iPhoneDuo` until the hardware identifier is confirmed, and omit the image. Display metrics remain unknown (`-1` for diagonal/ratio, `nil` for PPI); other unconfirmed capabilities use false/empty defaults. Automatic hardware detection requires a confirmed identifier. +- Recognize the `iPhone19,4` hardware identifier and omit the image. Display metrics remain unknown (`-1` for diagonal/ratio, `nil` for PPI); other unconfirmed capabilities use false/empty defaults. ## Version 5.9.0 diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index bf279dd7..f415c0e7 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -236,7 +236,7 @@ public enum Device { case iPhone18Pro /// Device is an [iPhone 18 Pro Max](https://support.apple.com/en-us/148591) case iPhone18ProMax - /// Provisional iPhone Duo; identifier, display metrics, and other capabilities await verification. + /// Provisional iPhone Duo; display metrics and other capabilities await verification. case iPhoneDuo /// Device is an [iPad 2](https://support.apple.com/kb/SP622) /// @@ -640,7 +640,7 @@ public enum Device { case "iPhone18,5": return iPhone17e case "iPhone19,2": return iPhone18Pro case "iPhone19,3", "iPhone19,7": return iPhone18ProMax - case "placeholder:iPhoneDuo": return iPhoneDuo + case "iPhone19,4": return iPhoneDuo case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return iPad2 case "iPad3,1", "iPad3,2", "iPad3,3": return iPad3 case "iPad3,4", "iPad3,5", "iPad3,6": return iPad4 diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index b7aa32b0..e7e27050 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -288,7 +288,7 @@ iPhones = [ hasEsimSupport=True, hasDualEsimSupport=True), # Duo capabilities other than its chip and foldability are unconfirmed (false/empty defaults). - Device("iPhoneDuo", "Provisional iPhone Duo; identifier, display metrics, and other capabilities await verification.", "", ["placeholder:iPhoneDuo"], -1, (-1, -1), "iPhone Duo", "iPhone Duo", "nil", + Device("iPhoneDuo", "Provisional iPhone Duo; display metrics and other capabilities await verification.", "", ["iPhone19,4"], -1, (-1, -1), "iPhone Duo", "iPhone Duo", "nil", cpu="a20Pro", isFoldable=True), ] diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 936bbad7..8685625b 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -33,7 +33,8 @@ class DeviceKitTests: XCTestCase { #if os(iOS) func testProvisionalIPhoneDuo() { let model = Device.iPhoneDuo - XCTAssertEqual(Device.mapToDevice(identifier: "placeholder:iPhoneDuo"), model) + XCTAssertEqual(Device.mapToDevice(identifier: "iPhone19,4"), model) + XCTAssertEqual(Device.mapToDevice(identifier: "placeholder:iPhoneDuo"), .unknown("placeholder:iPhoneDuo")) XCTAssertEqual(model.description, "iPhone Duo") XCTAssertEqual(model.safeDescription, "iPhone Duo") XCTAssertTrue(Device.allPhones.contains(model)) @@ -68,8 +69,8 @@ class DeviceKitTests: XCTestCase { ] let chip = Device.CPU.a20Pro XCTAssertEqual(chip.description, "A20 Pro") - // The deferred model and unverified identifiers must remain unknown. - for identifier in ["iPhone19,1", "iPhone19,4", "iPhone19,5", "iPhone19,6", "placeholder:iPhone18Pro", "placeholder:iPhone18ProMax"] { + // Unverified identifiers must remain unknown. + for identifier in ["iPhone19,1", "iPhone19,5", "iPhone19,6", "placeholder:iPhone18Pro", "placeholder:iPhone18ProMax"] { XCTAssertEqual(Device.mapToDevice(identifier: identifier), .unknown(identifier)) } #else From 1b48a59b0fe0c93ccf2db5392957bc5615ae5068 Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Mon, 12 Oct 2026 15:33:24 +0200 Subject: [PATCH 03/11] Recognize simulators of unknown device models --- CHANGELOG.md | 4 ++++ Source/Device.generated.swift | 7 +++++-- Source/Device.swift.gyb | 7 +++++-- Tests/Tests.swift | 30 ++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 650e5544..ddea5e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Add `Device.iPhoneDuo` with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. - Recognize the `iPhone19,4` hardware identifier and omit the image. Display metrics remain unknown (`-1` for diagonal/ratio, `nil` for PPI); other unconfirmed capabilities use false/empty defaults. +### Bug fixes + +- Make `isSimulator` return `true` for simulators of unknown devices, so detecting a simulator does not require support for its model. + ## Version 5.9.0 Releasedate: 2026-09-22 diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index f415c0e7..d54aacb1 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -1397,10 +1397,13 @@ public enum Device { return allRealDevices.map(Device.simulator) } - /// Returns whether the device is any of the simulator + /// Returns whether the device is a simulator, including simulators of unknown devices. /// Useful when there is a need to check and skip running a portion of code (location request or others) public var isSimulator: Bool { - return isOneOf(Device.allSimulators) + if case .simulator = self { + return true + } + return false } /** diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index e7e27050..e878903e 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -1228,10 +1228,13 @@ public enum Device { return allRealDevices.map(Device.simulator) } - /// Returns whether the device is any of the simulator + /// Returns whether the device is a simulator, including simulators of unknown devices. /// Useful when there is a need to check and skip running a portion of code (location request or others) public var isSimulator: Bool { - return isOneOf(Device.allSimulators) + if case .simulator = self { + return true + } + return false } /** diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 8685625b..74e298f9 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -128,6 +128,36 @@ class DeviceKitTests: XCTestCase { #endif } + func testIsSimulatorForKnownAndUnknownDevices() { + for model in Device.allRealDevices + [.unknown("unrecognized")] { + XCTAssertFalse(model.isSimulator, model.description) + XCTAssertTrue(Device.simulator(model).isSimulator, model.description) + } + } + + #if os(iOS) && targetEnvironment(simulator) + func testCurrentSimulatorWithKnownAndUnknownModels() { + let originalModelIdentifier = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] + defer { + if let originalModelIdentifier = originalModelIdentifier { + setenv("SIMULATOR_MODEL_IDENTIFIER", originalModelIdentifier, 1) + } else { + unsetenv("SIMULATOR_MODEL_IDENTIFIER") + } + } + + let models: [(String, Device)] = [ + ("iPhone19,4", .iPhoneDuo), + ("iPhone999,1", .unknown("iPhone999,1")) + ] + for (identifier, model) in models { + setenv("SIMULATOR_MODEL_IDENTIFIER", identifier, 1) + XCTAssertEqual(Device.current, .simulator(model), identifier) + XCTAssertTrue(Device.current.isSimulator, identifier) + } + } + #endif + func testDeviceDescription() { #if os(macOS) #else From 327c39657e254b9db2e15b622f8d2f6f9cbdd7b8 Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 27 Oct 2026 07:45:55 +0100 Subject: [PATCH 04/11] Prepare changelog for version 5.10.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddea5e55..02b9481a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## Version 5.10.0 + +Releasedate: TBD + +```ruby +pod 'DeviceKit', '~> 5.10' +``` + ### Provisional iPhone Duo support - Add `Device.iPhoneDuo` with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. From b226bf0ce8e53ffc649fe5640d37fc3935dda10c Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 27 Oct 2026 12:12:52 +0100 Subject: [PATCH 05/11] Add Apple Pencil USB-C support for iPhone Duo --- CHANGELOG.md | 1 + Source/Device.generated.swift | 3 ++- Source/Device.swift.gyb | 5 +++-- Tests/Tests.swift | 8 ++++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b9481a..c7389e52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ pod 'DeviceKit', '~> 5.10' ### Provisional iPhone Duo support - Add `Device.iPhoneDuo` with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. +- Report Apple Pencil (USB-C) support (`.firstGenerationUsbC`) for iPhone Duo and its simulator, and include Duo in `allApplePencilCapableDevices`. Other Apple Pencil models are not supported. - Recognize the `iPhone19,4` hardware identifier and omit the image. Display metrics remain unknown (`-1` for diagonal/ratio, `nil` for PPI); other unconfirmed capabilities use false/empty defaults. ### Bug fixes diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index d54aacb1..28765076 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -2294,7 +2294,7 @@ extension Device { /// All Apple Pencil Capable Devices public static var allApplePencilCapableDevices: [Device] { - return [.iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPhoneDuo, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// Returns supported version of the Apple Pencil @@ -2310,6 +2310,7 @@ extension Device { case .iPadPro12Inch: return .firstGeneration case .iPadPro12Inch2: return .firstGeneration case .iPadPro10Inch: return .firstGeneration + case .iPhoneDuo: return .firstGenerationUsbC case .iPad10: return [.firstGeneration, .firstGenerationUsbC] case .iPadA16: return [.firstGeneration, .firstGenerationUsbC] case .iPadAir4: return [.secondGeneration, .firstGenerationUsbC] diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index e878903e..4462d476 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -287,9 +287,10 @@ iPhones = [ has5gSupport=True, hasEsimSupport=True, hasDualEsimSupport=True), - # Duo capabilities other than its chip and foldability are unconfirmed (false/empty defaults). + # Duo capabilities other than its chip, foldability, and Apple Pencil support are unconfirmed (false/empty defaults). + # Apple Pencil (USB-C) only: https://www.apple.com/iphone-duo/specs/ Device("iPhoneDuo", "Provisional iPhone Duo; display metrics and other capabilities await verification.", "", ["iPhone19,4"], -1, (-1, -1), "iPhone Duo", "iPhone Duo", "nil", - cpu="a20Pro", isFoldable=True), + cpu="a20Pro", applePencilSupport=3, isFoldable=True), ] iPads = [ diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 74e298f9..8330e56a 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -922,6 +922,14 @@ class DeviceKitTests: XCTestCase { } func testApplePencilSupport() { + // iPhone Duo supports only Apple Pencil (USB-C), including its simulator representation. + XCTAssertTrue(Device.allApplePencilCapableDevices.contains(.iPhoneDuo)) + for model in [Device.iPhoneDuo, .simulator(.iPhoneDuo)] { + XCTAssertEqual(model.applePencilSupport, [.firstGenerationUsbC]) + } + XCTAssertFalse(Device.allApplePencilCapableDevices.contains(.iPhone18Pro)) + XCTAssertTrue(Device.iPhone18Pro.applePencilSupport.isEmpty) + // Test iPad (10th gen) - Supports Apple Pencil (1st gen) and Apple Pencil (USB-C) XCTAssertTrue(Device.iPad10.applePencilSupport.contains(.firstGeneration)) XCTAssertTrue(Device.iPad10.applePencilSupport.contains(.firstGenerationUsbC)) From 49bfcec400061c83617790416416b4b2aabd5eb5 Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 27 Oct 2026 12:19:06 +0100 Subject: [PATCH 06/11] Describe iPhone Duo support without provisional label --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7389e52..04fba70c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,11 @@ Releasedate: TBD pod 'DeviceKit', '~> 5.10' ``` -### Provisional iPhone Duo support +### iPhone Duo support -- Add `Device.iPhoneDuo` with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. +- Add support for iPhone Duo (`Device.iPhoneDuo`, hardware identifier `iPhone19,4`) with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. - Report Apple Pencil (USB-C) support (`.firstGenerationUsbC`) for iPhone Duo and its simulator, and include Duo in `allApplePencilCapableDevices`. Other Apple Pencil models are not supported. -- Recognize the `iPhone19,4` hardware identifier and omit the image. Display metrics remain unknown (`-1` for diagonal/ratio, `nil` for PPI); other unconfirmed capabilities use false/empty defaults. +- Display metrics are not yet provided (`-1` for diagonal/ratio, `nil` for PPI), and no device image is included. Other unconfirmed capabilities use false/empty defaults. ### Bug fixes From adea2d7a5c671592b40b60d136c9551d8cc229c3 Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 27 Oct 2026 12:19:32 +0100 Subject: [PATCH 07/11] Set 5.10.0 release date to October 27, 2026 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04fba70c..eb4c612d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## Version 5.10.0 -Releasedate: TBD +Releasedate: 2026-10-27 ```ruby pod 'DeviceKit', '~> 5.10' From 5cc19a4543ce872964960fd40cb66363d24f7acd Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 27 Oct 2026 12:20:25 +0100 Subject: [PATCH 08/11] Use standard release notes format for iPhone Duo --- CHANGELOG.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb4c612d..15206ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,23 @@ Releasedate: 2026-10-27 pod 'DeviceKit', '~> 5.10' ``` -### iPhone Duo support +### New devices + +This version adds support for iPhone Duo, including model identification, the A20 Pro chip, and Apple Pencil (USB-C) compatibility: ([#507](https://github.com/devicekit/DeviceKit/pull/507)) + +| Device | Case value | +| --- | --- | +| iPhone Duo | `Device.iPhoneDuo` | + +Display metrics are not yet available for this model. + +### New features -- Add support for iPhone Duo (`Device.iPhoneDuo`, hardware identifier `iPhone19,4`) with the A20 Pro chip and `isFoldable`, true only for iPhone Duo and its simulator. -- Report Apple Pencil (USB-C) support (`.firstGenerationUsbC`) for iPhone Duo and its simulator, and include Duo in `allApplePencilCapableDevices`. Other Apple Pencil models are not supported. -- Display metrics are not yet provided (`-1` for diagonal/ratio, `nil` for PPI), and no device image is included. Other unconfirmed capabilities use false/empty defaults. +- Add `isFoldable`, true for iPhone Duo and its simulator. ([#507](https://github.com/devicekit/DeviceKit/pull/507)) ### Bug fixes -- Make `isSimulator` return `true` for simulators of unknown devices, so detecting a simulator does not require support for its model. +- Fix `isSimulator` returning `false` for simulators of unknown devices. ([#507](https://github.com/devicekit/DeviceKit/pull/507)) ## Version 5.9.0 From 63473e3e6ae3835ecac39702febb217762b683ad Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 27 Oct 2026 20:59:58 +0100 Subject: [PATCH 09/11] Use iPhone Duo outer display diagonal and aspect ratio --- CHANGELOG.md | 2 +- Source/Device.generated.swift | 6 +++--- Source/Device.swift.gyb | 6 +++--- Tests/Tests.swift | 9 +++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15206ac5..a48a44f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ This version adds support for iPhone Duo, including model identification, the A2 | --- | --- | | iPhone Duo | `Device.iPhoneDuo` | -Display metrics are not yet available for this model. +The diagonal and screen ratio describe the outer display: 5.4 inches and 233:339 (1398 × 2034 pixels). PPI is not yet provided. ### New features diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index 28765076..af7bbfec 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -236,7 +236,7 @@ public enum Device { case iPhone18Pro /// Device is an [iPhone 18 Pro Max](https://support.apple.com/en-us/148591) case iPhone18ProMax - /// Provisional iPhone Duo; display metrics and other capabilities await verification. + /// Device is an [iPhone Duo](https://www.apple.com/iphone-duo/specs/). The diagonal and screen ratio describe the outer display. case iPhoneDuo /// Device is an [iPad 2](https://support.apple.com/kb/SP622) /// @@ -834,7 +834,7 @@ public enum Device { case .iPhone17e: return 6.1 case .iPhone18Pro: return 6.3 case .iPhone18ProMax: return 6.9 - case .iPhoneDuo: return -1 + case .iPhoneDuo: return 5.4 case .iPad2: return 9.7 case .iPad3: return 9.7 case .iPad4: return 9.7 @@ -986,7 +986,7 @@ public enum Device { case .iPhone17e: return (width: 9, height: 19.5) case .iPhone18Pro: return (width: 9, height: 19.5) case .iPhone18ProMax: return (width: 9, height: 19.5) - case .iPhoneDuo: return (width: -1, height: -1) + case .iPhoneDuo: return (width: 233, height: 339) case .iPad2: return (width: 3, height: 4) case .iPad3: return (width: 3, height: 4) case .iPad4: return (width: 3, height: 4) diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index 4462d476..69c997f0 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -287,9 +287,9 @@ iPhones = [ has5gSupport=True, hasEsimSupport=True, hasDualEsimSupport=True), - # Duo capabilities other than its chip, foldability, and Apple Pencil support are unconfirmed (false/empty defaults). - # Apple Pencil (USB-C) only: https://www.apple.com/iphone-duo/specs/ - Device("iPhoneDuo", "Provisional iPhone Duo; display metrics and other capabilities await verification.", "", ["iPhone19,4"], -1, (-1, -1), "iPhone Duo", "iPhone Duo", "nil", + # Use the outer display's diagonal and 1398 x 2034 pixel aspect ratio (reduced by 6). + # Other unverified capabilities retain false/empty defaults. Apple Pencil (USB-C) only. + Device("iPhoneDuo", "Device is an [iPhone Duo](https://www.apple.com/iphone-duo/specs/). The diagonal and screen ratio describe the outer display.", "", ["iPhone19,4"], 5.4, (233, 339), "iPhone Duo", "iPhone Duo", "nil", cpu="a20Pro", applePencilSupport=3, isFoldable=True), ] diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 8330e56a..39ac7e06 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -31,7 +31,7 @@ class DeviceKitTests: XCTestCase { } #if os(iOS) - func testProvisionalIPhoneDuo() { + func testIPhoneDuo() { let model = Device.iPhoneDuo XCTAssertEqual(Device.mapToDevice(identifier: "iPhone19,4"), model) XCTAssertEqual(Device.mapToDevice(identifier: "placeholder:iPhoneDuo"), .unknown("placeholder:iPhoneDuo")) @@ -45,9 +45,10 @@ class DeviceKitTests: XCTestCase { XCTAssertFalse(variant.isPad) XCTAssertTrue(variant.isFoldable) XCTAssertEqual(variant.cpu, .a20Pro) - XCTAssertEqual(variant.diagonal, -1) - XCTAssertEqual(variant.screenRatio.width, -1) - XCTAssertEqual(variant.screenRatio.height, -1) + // The single-display properties describe the outer display, not the unfolded inner display. + XCTAssertEqual(variant.diagonal, 5.4) + XCTAssertEqual(variant.screenRatio.width, 233) + XCTAssertEqual(variant.screenRatio.height, 339) XCTAssertNil(variant.ppi) } } From c98e62378b17c738a04e873aad373ebc0be1fa4b Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Tue, 27 Oct 2026 21:04:00 +0100 Subject: [PATCH 10/11] Complete iPhone Duo hardware specifications --- CHANGELOG.md | 4 ++-- Source/Device.generated.swift | 35 +++++++++++++++++++---------------- Source/Device.swift.gyb | 11 +++++++---- Tests/Tests.swift | 26 +++++++++++++++++++++++++- 4 files changed, 53 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a48a44f9..3e1f4c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,13 @@ pod 'DeviceKit', '~> 5.10' ### New devices -This version adds support for iPhone Duo, including model identification, the A20 Pro chip, and Apple Pencil (USB-C) compatibility: ([#507](https://github.com/devicekit/DeviceKit/pull/507)) +This version adds support for iPhone Duo, including display metrics, the A20 Pro chip, Touch ID, wide and ultra-wide cameras, Dynamic Island, wireless charging, USB-C, 5G, dual eSIM, and Apple Pencil (USB-C) compatibility: ([#507](https://github.com/devicekit/DeviceKit/pull/507)) | Device | Case value | | --- | --- | | iPhone Duo | `Device.iPhoneDuo` | -The diagonal and screen ratio describe the outer display: 5.4 inches and 233:339 (1398 × 2034 pixels). PPI is not yet provided. +Display metrics describe the outer display: 5.4 inches, a 233:339 aspect ratio (1398 × 2034 pixels), and 460 PPI. ### New features diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index af7bbfec..b10138c7 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -236,7 +236,9 @@ public enum Device { case iPhone18Pro /// Device is an [iPhone 18 Pro Max](https://support.apple.com/en-us/148591) case iPhone18ProMax - /// Device is an [iPhone Duo](https://www.apple.com/iphone-duo/specs/). The diagonal and screen ratio describe the outer display. + /// Device is an [iPhone Duo](https://www.apple.com/iphone-duo/specs/). Display metrics and sensor housing describe the outer display. + /// + /// ![Image](https://www.apple.com/v/iphone-duo/e/images/overview/camera/hero_left__cpq2rhkwqzqu_large.jpg) case iPhoneDuo /// Device is an [iPad 2](https://support.apple.com/kb/SP622) /// @@ -1102,7 +1104,7 @@ public enum Device { /// All X-Series Devices @available(*, deprecated, renamed: "allDevicesWithSensorHousing") public static var allXSeriesDevices: [Device] { - return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax] + return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo] } /// All Plus and Max-Sized Devices @@ -1193,7 +1195,7 @@ public enum Device { /// All Touch ID Capable Devices public static var allTouchIDCapableDevices: [Device] { - return [.iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneSE2, .iPhoneSE3, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch] + return [.iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneSE2, .iPhoneSE3, .iPhoneDuo, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch] } /// All Face ID Capable Devices @@ -1203,7 +1205,7 @@ public enum Device { /// All Devices with Touch ID or Face ID public static var allBiometricAuthenticationCapableDevices: [Device] { - return [.iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// Returns whether or not the device has Touch ID @@ -1223,7 +1225,7 @@ public enum Device { /// All devices that feature a sensor housing in the screen public static var allDevicesWithSensorHousing: [Device] { - return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax] + return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo] } /// All simulator devices that feature a sensor housing in the screen @@ -1238,7 +1240,7 @@ public enum Device { /// All devices that feature a screen with rounded corners. public static var allDevicesWithRoundedDisplayCorners: [Device] { - return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// Returns whether or not the device has a screen with rounded corners. @@ -1248,7 +1250,7 @@ public enum Device { /// All devices that have the Dynamic Island. public static var allDevicesWithDynamicIsland: [Device] { - return [.iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone18Pro, .iPhone18ProMax] + return [.iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo] } /// Returns whether or not the device has the Dynamic Island. @@ -1268,7 +1270,7 @@ public enum Device { /// All devices that support wireless charging. public static var allDevicesWithWirelessChargingSupport: [Device] { - return [.iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax] + return [.iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo] } /// Returns whether or not the device supports wireless charging. @@ -1278,7 +1280,7 @@ public enum Device { /// All devices that support 5G. public static var allDevicesWith5gSupport: [Device] { - return [.iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPad10, .iPadA16, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPad10, .iPadA16, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// Returns whether or not the device has 5G support. @@ -1293,7 +1295,7 @@ public enum Device { /// model capabilities and does not guarantee eSIM availability on a particular device. /// See https://support.apple.com/en-us/109317 and https://support.apple.com/en-us/119592. public static var allDevicesWithEsimSupport: [Device] { - return [.iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// Returns whether the device model supports eSIM. Simulators use their simulated model. @@ -1306,7 +1308,7 @@ public enum Device { /// Support varies by country, region, and carrier; model membership does not guarantee /// eSIM availability on a particular device. These models also appear in `allDevicesWithEsimSupport`. public static var allDevicesWithDualEsimSupport: [Device] { - return [.iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax] + return [.iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo] } /// Returns whether the device model supports two active eSIMs simultaneously. @@ -1327,7 +1329,7 @@ public enum Device { /// All devices that have a USB-C connectivity. public static var allDevicesWithUSBCConnectivity: [Device] { - return [.iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini6, .iPadMiniA17Pro, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// Returns whether or not the device has a USB-C power supply. @@ -1572,7 +1574,7 @@ public enum Device { case .iPhone17e: return 460 case .iPhone18Pro: return 460 case .iPhone18ProMax: return 460 - case .iPhoneDuo: return nil + case .iPhoneDuo: return 460 case .iPad2: return 132 case .iPad3: return 264 case .iPad4: return 264 @@ -2434,6 +2436,7 @@ extension Device { case .iPhone16: return [.wide, .ultraWide] case .iPhone16Plus: return [.wide, .ultraWide] case .iPhone17: return [.wide, .ultraWide] + case .iPhoneDuo: return [.wide, .ultraWide] case .iPadPro11Inch2: return [.wide, .ultraWide] case .iPadPro12Inch4: return [.wide, .ultraWide] case .iPadPro11Inch3: return [.wide, .ultraWide] @@ -2462,7 +2465,7 @@ extension Device { /// All devices that feature a camera public static var allDevicesWithCamera: [Device] { - return [.iPodTouch5, .iPodTouch6, .iPodTouch7, .iPhone4, .iPhone4s, .iPhone5, .iPhone5c, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPodTouch5, .iPodTouch6, .iPodTouch7, .iPhone4, .iPhone4s, .iPhone5, .iPhone5c, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// All devices that feature a normal camera @@ -2473,7 +2476,7 @@ extension Device { /// All devices that feature a wide camera public static var allDevicesWithWideCamera: [Device] { - return [.iPodTouch5, .iPodTouch6, .iPodTouch7, .iPhone4, .iPhone4s, .iPhone5, .iPhone5c, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] + return [.iPodTouch5, .iPodTouch6, .iPodTouch7, .iPhone4, .iPhone4s, .iPhone5, .iPhone5c, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhoneSE2, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhoneSE3, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone16e, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhoneAir, .iPhone17e, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPad8, .iPad9, .iPad10, .iPadA16, .iPadAir4, .iPadAir5, .iPadAir11M2, .iPadAir13M2, .iPadAir11M3, .iPadAir13M3, .iPadAir11M4, .iPadAir13M4, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5, .iPadMini6, .iPadMiniA17Pro, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6, .iPadPro11M4, .iPadPro13M4, .iPadPro11M5, .iPadPro13M5] } /// All devices that feature a telephoto camera @@ -2483,7 +2486,7 @@ extension Device { /// All devices that feature an ultra wide camera public static var allDevicesWithUltraWideCamera: [Device] { - return [.iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6] + return [.iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPhone12, .iPhone12Mini, .iPhone12Pro, .iPhone12ProMax, .iPhone13, .iPhone13Mini, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus, .iPhone14Pro, .iPhone14ProMax, .iPhone15, .iPhone15Plus, .iPhone15Pro, .iPhone15ProMax, .iPhone16, .iPhone16Plus, .iPhone16Pro, .iPhone16ProMax, .iPhone17, .iPhone17Pro, .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, .iPhoneDuo, .iPadPro11Inch2, .iPadPro12Inch4, .iPadPro11Inch3, .iPadPro12Inch5, .iPadPro11Inch4, .iPadPro12Inch6] } /// Returns whether or not the current device has a camera diff --git a/Source/Device.swift.gyb b/Source/Device.swift.gyb index 69c997f0..af7f2821 100644 --- a/Source/Device.swift.gyb +++ b/Source/Device.swift.gyb @@ -287,10 +287,13 @@ iPhones = [ has5gSupport=True, hasEsimSupport=True, hasDualEsimSupport=True), - # Use the outer display's diagonal and 1398 x 2034 pixel aspect ratio (reduced by 6). - # Other unverified capabilities retain false/empty defaults. Apple Pencil (USB-C) only. - Device("iPhoneDuo", "Device is an [iPhone Duo](https://www.apple.com/iphone-duo/specs/). The diagonal and screen ratio describe the outer display.", "", ["iPhone19,4"], 5.4, (233, 339), "iPhone Duo", "iPhone Duo", "nil", - cpu="a20Pro", applePencilSupport=3, isFoldable=True), + # Display metrics and sensor housing describe the outer display (1398 x 2034 pixels). + # The 2x telephoto mode crops the main sensor; there is no separate telephoto camera. + Device("iPhoneDuo", "Device is an [iPhone Duo](https://www.apple.com/iphone-duo/specs/). Display metrics and sensor housing describe the outer display.", "https://www.apple.com/v/iphone-duo/e/images/overview/camera/hero_left__cpq2rhkwqzqu_large.jpg", ["iPhone19,4"], 5.4, (233, 339), "iPhone Duo", "iPhone Duo", 460, + cpu="a20Pro", isXSeries=True, hasTouchID=True, hasSensorHousing=True, + supportsWirelessCharging=True, hasRoundedDisplayCorners=True, hasDynamicIsland=True, + applePencilSupport=3, cameras=13, hasUSBCConnectivity=True, has5gSupport=True, + hasEsimSupport=True, hasDualEsimSupport=True, isFoldable=True), ] iPads = [ diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 39ac7e06..2809633a 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -40,6 +40,14 @@ class DeviceKitTests: XCTestCase { XCTAssertTrue(Device.allPhones.contains(model)) XCTAssertTrue(Device.allRealDevices.contains(model)) XCTAssertTrue(Device.allSimulators.contains(.simulator(model))) + XCTAssertEqual(model.cameras, [.wide, .ultraWide]) + XCTAssertTrue(model.hasCamera) + XCTAssertTrue(model.hasWideCamera) + XCTAssertTrue(model.hasUltraWideCamera) + XCTAssertFalse(model.hasTelephotoCamera) + XCTAssertTrue(Device.allDevicesWithWideCamera.contains(model)) + XCTAssertTrue(Device.allDevicesWithUltraWideCamera.contains(model)) + XCTAssertFalse(Device.allDevicesWithTelephotoCamera.contains(model)) for variant in [model, .simulator(model)] { XCTAssertTrue(variant.isPhone) XCTAssertFalse(variant.isPad) @@ -49,7 +57,20 @@ class DeviceKitTests: XCTestCase { XCTAssertEqual(variant.diagonal, 5.4) XCTAssertEqual(variant.screenRatio.width, 233) XCTAssertEqual(variant.screenRatio.height, 339) - XCTAssertNil(variant.ppi) + XCTAssertEqual(variant.ppi, 460) + XCTAssertTrue(variant.isTouchIDCapable) + XCTAssertTrue(variant.hasBiometricSensor) + XCTAssertFalse(variant.isFaceIDCapable) + XCTAssertTrue(variant.hasSensorHousing) + XCTAssertTrue(variant.hasRoundedDisplayCorners) + XCTAssertTrue(variant.hasDynamicIsland) + XCTAssertTrue(variant.supportsWirelessCharging) + XCTAssertTrue(variant.hasUSBCConnectivity) + XCTAssertTrue(variant.has5gSupport) + XCTAssertTrue(variant.hasEsimSupport) + XCTAssertTrue(variant.hasDualEsimSupport) + XCTAssertFalse(variant.hasLidarSensor) + XCTAssertFalse(variant.has3dTouchSupport) } } #endif @@ -676,6 +697,7 @@ class DeviceKitTests: XCTestCase { .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, + .iPhoneDuo, .iPhoneAir, ] for device in Device.allRealDevices { @@ -712,6 +734,7 @@ class DeviceKitTests: XCTestCase { .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, + .iPhoneDuo, .iPhoneAir, .iPhone17e, .iPad10, @@ -890,6 +913,7 @@ class DeviceKitTests: XCTestCase { .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, + .iPhoneDuo, .iPhoneAir, .iPhone17e, .iPad10, From 56274b1857e6eb99a4d722801d9ab1d2383f2b38 Mon Sep 17 00:00:00 2001 From: Zandor Smith Date: Sun, 1 Nov 2026 15:48:48 +0100 Subject: [PATCH 11/11] Update 5.10.0 installation examples and release date --- CHANGELOG.md | 2 +- README.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1f4c89..09804786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## Version 5.10.0 -Releasedate: 2026-10-27 +Releasedate: 2026-11-01 ```ruby pod 'DeviceKit', '~> 5.10' diff --git a/README.md b/README.md index 1abbcde7..2d29d234 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ `DeviceKit` is a value-type replacement of [`UIDevice`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/). -## Current version 5.9.0 +## Current version 5.10.0 See our detailed [changelog](CHANGELOG.md) for the latest features, improvements and bug fixes. ## Features @@ -52,7 +52,7 @@ DeviceKit can be installed in various ways. #### Swift 5 ```ruby -pod 'DeviceKit', '~> 5.2' +pod 'DeviceKit', '~> 5.10' ``` #### iOS 8.0 support ```ruby @@ -76,7 +76,7 @@ pod 'DeviceKit', :git => 'https://github.com/devicekit/DeviceKit.git', :branch = #### Swift 5 ```swift dependencies: [ - .package(url: "https://github.com/devicekit/DeviceKit.git", from: "4.0.0"), + .package(url: "https://github.com/devicekit/DeviceKit.git", from: "5.10.0"), /// ... ] ``` @@ -92,7 +92,7 @@ dependencies: [ #### Swift 5 ```ogdl -github "devicekit/DeviceKit" ~> 4.0 +github "devicekit/DeviceKit" ~> 5.10 ``` #### iOS 8.0 support ```ogdl