diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d8410d..09804786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ ## Unreleased +## Version 5.10.0 + +Releasedate: 2026-11-01 + +```ruby +pod 'DeviceKit', '~> 5.10' +``` + +### New devices + +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` | + +Display metrics describe the outer display: 5.4 inches, a 233:339 aspect ratio (1398 × 2034 pixels), and 460 PPI. + +### New features + +- Add `isFoldable`, true for iPhone Duo and its simulator. ([#507](https://github.com/devicekit/DeviceKit/pull/507)) + +### Bug fixes + +- Fix `isSimulator` returning `false` for simulators of unknown devices. ([#507](https://github.com/devicekit/DeviceKit/pull/507)) + ## Version 5.9.0 Releasedate: 2026-09-22 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 diff --git a/Source/Device.generated.swift b/Source/Device.generated.swift index 1fc55345..b10138c7 100644 --- a/Source/Device.generated.swift +++ b/Source/Device.generated.swift @@ -236,6 +236,10 @@ 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/). 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) /// /// ![Image](https://support.apple.com/library/APPLE/APPLECARE_ALLGEOS/SP622/SP622_01-ipad2-mul.png) @@ -638,6 +642,7 @@ public enum Device { case "iPhone18,5": return iPhone17e case "iPhone19,2": return iPhone18Pro case "iPhone19,3", "iPhone19,7": return iPhone18ProMax + 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 @@ -744,6 +749,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 +836,7 @@ public enum Device { case .iPhone17e: return 6.1 case .iPhone18Pro: return 6.3 case .iPhone18ProMax: return 6.9 + case .iPhoneDuo: return 5.4 case .iPad2: return 9.7 case .iPad3: return 9.7 case .iPad4: return 9.7 @@ -969,6 +988,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: 233, height: 339) case .iPad2: return (width: 3, height: 4) case .iPad3: return (width: 3, height: 4) case .iPad4: return (width: 3, height: 4) @@ -1073,7 +1093,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 @@ -1084,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 @@ -1175,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 @@ -1185,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 @@ -1205,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 @@ -1220,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. @@ -1230,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. @@ -1250,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. @@ -1260,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. @@ -1275,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. @@ -1288,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. @@ -1309,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. @@ -1379,10 +1399,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 } /** @@ -1551,6 +1574,7 @@ public enum Device { case .iPhone17e: return 460 case .iPhone18Pro: return 460 case .iPhone18ProMax: return 460 + case .iPhoneDuo: return 460 case .iPad2: return 132 case .iPad3: return 264 case .iPad4: return 264 @@ -1734,6 +1758,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 +1929,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)" @@ -2270,7 +2296,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 @@ -2286,6 +2312,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] @@ -2409,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] @@ -2437,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 @@ -2448,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 @@ -2458,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 @@ -2634,6 +2662,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..af7f2821 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,14 @@ iPhones = [ hasDynamicIsland=True, cameras=123, hasLidarSensor=True, hasUSBCConnectivity=True, has5gSupport=True, hasEsimSupport=True, hasDualEsimSupport=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 = [ @@ -828,6 +838,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. @@ -1205,10 +1232,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 ce99bbc6..2809633a 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -16,6 +16,65 @@ 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 testIPhoneDuo() { + let model = Device.iPhoneDuo + 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)) + 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) + XCTAssertTrue(variant.isFoldable) + XCTAssertEqual(variant.cpu, .a20Pro) + // 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) + 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 + #if os(iOS) || os(watchOS) func testSeptember2026Devices() { struct ExpectedDevice { @@ -32,8 +91,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 @@ -91,6 +150,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 @@ -608,6 +697,7 @@ class DeviceKitTests: XCTestCase { .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, + .iPhoneDuo, .iPhoneAir, ] for device in Device.allRealDevices { @@ -644,6 +734,7 @@ class DeviceKitTests: XCTestCase { .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, + .iPhoneDuo, .iPhoneAir, .iPhone17e, .iPad10, @@ -822,6 +913,7 @@ class DeviceKitTests: XCTestCase { .iPhone17ProMax, .iPhone18Pro, .iPhone18ProMax, + .iPhoneDuo, .iPhoneAir, .iPhone17e, .iPad10, @@ -855,6 +947,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))