Skip to content

Instantly share code, notes, and snippets.

@AmirDaliri
Created October 22, 2019 14:10
Show Gist options
  • Save AmirDaliri/c09f55dcb17016f7dc60a76031c5ffe9 to your computer and use it in GitHub Desktop.
Save AmirDaliri/c09f55dcb17016f7dc60a76031c5ffe9 to your computer and use it in GitHub Desktop.
All iOS Device :)
//
// Device.swift
// ISKUR
//
// Created by Amir Daliri on 10/22/19.
// Copyright © 2019 Mobilion. All rights reserved.
//
#if os(watchOS)
import WatchKit
#else
import UIKit
#endif
// MARK: - Device
/// This enum is a value-type wrapper and extension of
///
/// Usage:
///
/// let device = Device.current
///
/// print(device) // prints, for example, "iPhone 6 Plus"
///
/// if device == .iPhone6Plus {
/// // Do something
/// } else {
/// // Do something else
/// }
///
/// ...
///
/// if device.batteryState == .full || device.batteryState >= .charging(75) {
/// print("Your battery is happy! 😊")
/// }
///
/// ...
///
/// if device.batteryLevel >= 50 {
/// install_iOS()
/// } else {
/// showError()
/// }
///
public enum Device {
#if os(iOS)
case iPodTouch5
case iPodTouch6
case iPodTouch7
case iPhone4
case iPhone4s
case iPhone5
case iPhone5c
case iPhone5s
case iPhone6
case iPhone6Plus
case iPhone6s
case iPhone6sPlus
case iPhone7
case iPhone7Plus
case iPhoneSE
case iPhone8
case iPhone8Plus
case iPhoneX
case iPhoneXS
case iPhoneXSMax
case iPhoneXR
case iPhone11
case iPhone11Pro
case iPhone11ProMax
case iPad2
case iPad3
case iPad4
case iPadAir
case iPadAir2
case iPad5
case iPad6
case iPadAir3
case iPad7
case iPadMini
case iPadMini2
case iPadMini3
case iPadMini4
case iPadMini5
case iPadPro9Inch
case iPadPro12Inch
case iPadPro12Inch2
case iPadPro10Inch
case iPadPro11Inch
case iPadPro12Inch3
case homePod
#elseif os(tvOS)
case appleTVHD
case appleTV4K
#elseif os(watchOS)
case appleWatchSeries0_38mm
case appleWatchSeries0_42mm
case appleWatchSeries1_38mm
case appleWatchSeries1_42mm
case appleWatchSeries2_38mm
case appleWatchSeries2_42mm
case appleWatchSeries3_38mm
case appleWatchSeries3_42mm
case appleWatchSeries4_40mm
case appleWatchSeries4_44mm
case appleWatchSeries5_40mm
case appleWatchSeries5_44mm
#endif
indirect case simulator(Device)
case unknown(String)
/// Returns a `Device` representing the current device this software runs on.
public static var current: Device {
return Device.mapToDevice(identifier: Device.identifier)
}
/// Gets the identifier from the system, such as "iPhone7,1".
public static var identifier: String = {
var systemInfo = utsname()
uname(&systemInfo)
let mirror = Mirror(reflecting: systemInfo.machine)
let identifier = mirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}()
/// Maps an identifier to a Device. If the identifier can not be mapped to an existing device, `UnknownDevice(identifier)` is returned.
///
/// - parameter identifier: The device identifier, e.g. "iPhone7,1". Can be obtained from `Device.identifier`.
///
/// - returns: An initialized `Device`.
public static func mapToDevice(identifier: String) -> Device { // swiftlint:disable:this cyclomatic_complexity function_body_length
#if os(iOS)
switch identifier {
case "iPod5,1": return iPodTouch5
case "iPod7,1": return iPodTouch6
case "iPod9,1": return iPodTouch7
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return iPhone4
case "iPhone4,1": return iPhone4s
case "iPhone5,1", "iPhone5,2": return iPhone5
case "iPhone5,3", "iPhone5,4": return iPhone5c
case "iPhone6,1", "iPhone6,2": return iPhone5s
case "iPhone7,2": return iPhone6
case "iPhone7,1": return iPhone6Plus
case "iPhone8,1": return iPhone6s
case "iPhone8,2": return iPhone6sPlus
case "iPhone9,1", "iPhone9,3": return iPhone7
case "iPhone9,2", "iPhone9,4": return iPhone7Plus
case "iPhone8,4": return iPhoneSE
case "iPhone10,1", "iPhone10,4": return iPhone8
case "iPhone10,2", "iPhone10,5": return iPhone8Plus
case "iPhone10,3", "iPhone10,6": return iPhoneX
case "iPhone11,2": return iPhoneXS
case "iPhone11,4", "iPhone11,6": return iPhoneXSMax
case "iPhone11,8": return iPhoneXR
case "iPhone12,1": return iPhone11
case "iPhone12,3": return iPhone11Pro
case "iPhone12,5": return iPhone11ProMax
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
case "iPad4,1", "iPad4,2", "iPad4,3": return iPadAir
case "iPad5,3", "iPad5,4": return iPadAir2
case "iPad6,11", "iPad6,12": return iPad5
case "iPad7,5", "iPad7,6": return iPad6
case "iPad11,3", "iPad11,4": return iPadAir3
case "iPad7,11", "iPad7,12": return iPad7
case "iPad2,5", "iPad2,6", "iPad2,7": return iPadMini
case "iPad4,4", "iPad4,5", "iPad4,6": return iPadMini2
case "iPad4,7", "iPad4,8", "iPad4,9": return iPadMini3
case "iPad5,1", "iPad5,2": return iPadMini4
case "iPad11,1", "iPad11,2": return iPadMini5
case "iPad6,3", "iPad6,4": return iPadPro9Inch
case "iPad6,7", "iPad6,8": return iPadPro12Inch
case "iPad7,1", "iPad7,2": return iPadPro12Inch2
case "iPad7,3", "iPad7,4": return iPadPro10Inch
case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": return iPadPro11Inch
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": return iPadPro12Inch3
case "AudioAccessory1,1": return homePod
case "i386", "x86_64": return simulator(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))
default: return unknown(identifier)
}
#elseif os(tvOS)
switch identifier {
case "AppleTV5,3": return appleTVHD
case "AppleTV6,2": return appleTV4K
case "i386", "x86_64": return simulator(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))
default: return unknown(identifier)
}
#elseif os(watchOS)
switch identifier {
case "Watch1,1": return appleWatchSeries0_38mm
case "Watch1,2": return appleWatchSeries0_42mm
case "Watch2,6": return appleWatchSeries1_38mm
case "Watch2,7": return appleWatchSeries1_42mm
case "Watch2,3": return appleWatchSeries2_38mm
case "Watch2,4": return appleWatchSeries2_42mm
case "Watch3,1", "Watch3,3": return appleWatchSeries3_38mm
case "Watch3,2", "Watch3,4": return appleWatchSeries3_42mm
case "Watch4,1", "Watch4,3": return appleWatchSeries4_40mm
case "Watch4,2", "Watch4,4": return appleWatchSeries4_44mm
case "Watch5,1", "Watch5,3": return appleWatchSeries5_40mm
case "Watch5,2", "Watch5,4": return appleWatchSeries5_44mm
case "i386", "x86_64": return simulator(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "watchOS"))
default: return unknown(identifier)
}
#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.
///
/// - parameter device: A device.
///
/// - returns: the underlying device If the `device` is a `simulator`,
/// otherwise return the `device`.
public static func realDevice(from device: Device) -> Device {
if case let .simulator(model) = device {
return model
}
return device
}
#if os(iOS) || os(watchOS)
/// Returns diagonal screen length in inches
public var diagonal: Double {
#if os(iOS)
switch self {
case .iPodTouch5: return 4
case .iPodTouch6: return 4
case .iPodTouch7: return 4
case .iPhone4: return 3.5
case .iPhone4s: return 3.5
case .iPhone5: return 4
case .iPhone5c: return 4
case .iPhone5s: return 4
case .iPhone6: return 4.7
case .iPhone6Plus: return 5.5
case .iPhone6s: return 4.7
case .iPhone6sPlus: return 5.5
case .iPhone7: return 4.7
case .iPhone7Plus: return 5.5
case .iPhoneSE: return 4
case .iPhone8: return 4.7
case .iPhone8Plus: return 5.5
case .iPhoneX: return 5.8
case .iPhoneXS: return 5.8
case .iPhoneXSMax: return 6.5
case .iPhoneXR: return 6.1
case .iPhone11: return 6.1
case .iPhone11Pro: return 5.8
case .iPhone11ProMax: return 6.5
case .iPad2: return 9.7
case .iPad3: return 9.7
case .iPad4: return 9.7
case .iPadAir: return 9.7
case .iPadAir2: return 9.7
case .iPad5: return 9.7
case .iPad6: return 9.7
case .iPadAir3: return 10.5
case .iPad7: return 10.2
case .iPadMini: return 7.9
case .iPadMini2: return 7.9
case .iPadMini3: return 7.9
case .iPadMini4: return 7.9
case .iPadMini5: return 7.9
case .iPadPro9Inch: return 9.7
case .iPadPro12Inch: return 12.9
case .iPadPro12Inch2: return 12.9
case .iPadPro10Inch: return 10.5
case .iPadPro11Inch: return 11.0
case .iPadPro12Inch3: return 12.9
case .homePod: return -1
case .simulator(let model): return model.diagonal
case .unknown: return -1
}
#elseif os(watchOS)
switch self {
case .appleWatchSeries0_38mm: return 1.5
case .appleWatchSeries0_42mm: return 1.6
case .appleWatchSeries1_38mm: return 1.5
case .appleWatchSeries1_42mm: return 1.6
case .appleWatchSeries2_38mm: return 1.5
case .appleWatchSeries2_42mm: return 1.6
case .appleWatchSeries3_38mm: return 1.5
case .appleWatchSeries3_42mm: return 1.6
case .appleWatchSeries4_40mm: return 1.8
case .appleWatchSeries4_44mm: return 2.0
case .appleWatchSeries5_40mm: return 1.8
case .appleWatchSeries5_44mm: return 2.0
case .simulator(let model): return model.diagonal
case .unknown: return -1
}
#endif
}
#endif
/// Returns screen ratio as a tuple
public var screenRatio: (width: Double, height: Double) {
#if os(iOS)
switch self {
case .iPodTouch5: return (width: 9, height: 16)
case .iPodTouch6: return (width: 9, height: 16)
case .iPodTouch7: return (width: 9, height: 16)
case .iPhone4: return (width: 2, height: 3)
case .iPhone4s: return (width: 2, height: 3)
case .iPhone5: return (width: 9, height: 16)
case .iPhone5c: return (width: 9, height: 16)
case .iPhone5s: return (width: 9, height: 16)
case .iPhone6: return (width: 9, height: 16)
case .iPhone6Plus: return (width: 9, height: 16)
case .iPhone6s: return (width: 9, height: 16)
case .iPhone6sPlus: return (width: 9, height: 16)
case .iPhone7: return (width: 9, height: 16)
case .iPhone7Plus: return (width: 9, height: 16)
case .iPhoneSE: return (width: 9, height: 16)
case .iPhone8: return (width: 9, height: 16)
case .iPhone8Plus: return (width: 9, height: 16)
case .iPhoneX: return (width: 9, height: 19.5)
case .iPhoneXS: return (width: 9, height: 19.5)
case .iPhoneXSMax: return (width: 9, height: 19.5)
case .iPhoneXR: return (width: 9, height: 19.5)
case .iPhone11: return (width: 9, height: 19.5)
case .iPhone11Pro: return (width: 9, height: 19.5)
case .iPhone11ProMax: return (width: 9, height: 19.5)
case .iPad2: return (width: 3, height: 4)
case .iPad3: return (width: 3, height: 4)
case .iPad4: return (width: 3, height: 4)
case .iPadAir: return (width: 3, height: 4)
case .iPadAir2: return (width: 3, height: 4)
case .iPad5: return (width: 3, height: 4)
case .iPad6: return (width: 3, height: 4)
case .iPadAir3: return (width: 3, height: 4)
case .iPad7: return (width: 3, height: 4)
case .iPadMini: return (width: 3, height: 4)
case .iPadMini2: return (width: 3, height: 4)
case .iPadMini3: return (width: 3, height: 4)
case .iPadMini4: return (width: 3, height: 4)
case .iPadMini5: return (width: 3, height: 4)
case .iPadPro9Inch: return (width: 3, height: 4)
case .iPadPro12Inch: return (width: 3, height: 4)
case .iPadPro12Inch2: return (width: 3, height: 4)
case .iPadPro10Inch: return (width: 3, height: 4)
case .iPadPro11Inch: return (width: 139, height: 199)
case .iPadPro12Inch3: return (width: 512, height: 683)
case .homePod: return (width: 4, height: 5)
case .simulator(let model): return model.screenRatio
case .unknown: return (width: -1, height: -1)
}
#elseif os(watchOS)
switch self {
case .appleWatchSeries0_38mm: return (width: 4, height: 5)
case .appleWatchSeries0_42mm: return (width: 4, height: 5)
case .appleWatchSeries1_38mm: return (width: 4, height: 5)
case .appleWatchSeries1_42mm: return (width: 4, height: 5)
case .appleWatchSeries2_38mm: return (width: 4, height: 5)
case .appleWatchSeries2_42mm: return (width: 4, height: 5)
case .appleWatchSeries3_38mm: return (width: 4, height: 5)
case .appleWatchSeries3_42mm: return (width: 4, height: 5)
case .appleWatchSeries4_40mm: return (width: 4, height: 5)
case .appleWatchSeries4_44mm: return (width: 4, height: 5)
case .appleWatchSeries5_40mm: return (width: 4, height: 5)
case .appleWatchSeries5_44mm: return (width: 4, height: 5)
case .simulator(let model): return model.screenRatio
case .unknown: return (width: -1, height: -1)
}
#elseif os(tvOS)
return (width: -1, height: -1)
#endif
}
#if os(iOS)
/// All iPods
public static var allPods: [Device] {
return [.iPodTouch5, .iPodTouch6, .iPodTouch7]
}
/// 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]
}
/// All iPads
public static var allPads: [Device] {
return [.iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3]
}
/// All X-Series Devices
public static var allXSeriesDevices: [Device] {
return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .simulator(.iPhoneXS), .simulator(.iPhoneXSMax), .simulator(.iPhoneXR), .iPhone11, .iPhone11Pro, .iPhone11ProMax]
}
/// All Plus-Sized Devices
public static var allPlusSizedDevices: [Device] {
return [.iPhone6Plus, .iPhone6sPlus, .iPhone7Plus, .iPhone8Plus, .simulator(.iPhone6Plus), .simulator(.iPhone6sPlus), .simulator(.iPhone7Plus), .simulator(.iPhone8Plus), .iPhoneXSMax, .iPhone11ProMax]
}
/// All Pro Devices
public static var allProDevices: [Device] {
return [.iPhone11Pro, .iPhone11ProMax, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3]
}
/// All mini Devices
public static var allMiniDevices: [Device] {
return [.iPhone5, .iPhone5s, .iPhoneSE, .iPhoneSE, .simulator(.iPhone5)]
// return [.iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5]
}
/// All normal Devices
public static var allNormalDevices: [Device] {
return [.iPhone6, .iPhone6s, .iPhone7, .iPhone8, .simulator(.iPhone6)]
}
/// All simulator iPods
public static var allSimulatorPods: [Device] {
return allPods.map(Device.simulator)
}
/// All simulator iPhones
public static var allSimulatorPhones: [Device] {
return allPhones.map(Device.simulator)
}
/// All simulator iPads
public static var allSimulatorPads: [Device] {
return allPads.map(Device.simulator)
}
/// All simulator iPad mini
public static var allSimulatorMiniDevices: [Device] {
return allMiniDevices.map(Device.simulator)
}
/// All simulator Plus-Sized Devices
public static var allSimulatorXSeriesDevices: [Device] {
return allXSeriesDevices.map(Device.simulator)
}
/// All simulator Plus-Sized Devices
public static var allSimulatorPlusSizedDevices: [Device] {
return allPlusSizedDevices.map(Device.simulator)
}
/// All simulator Pro Devices
public static var allSimulatorProDevices: [Device] {
return allProDevices.map(Device.simulator)
}
/// Returns whether the device is an iPod (real or simulator)
public var isPod: Bool {
return isOneOf(Device.allPods) || isOneOf(Device.allSimulatorPods)
}
/// Returns whether the device is an iPhone (real or simulator)
public var isPhone: Bool {
return (isOneOf(Device.allPhones)
|| isOneOf(Device.allSimulatorPhones)
|| (UIDevice.current.userInterfaceIdiom == .phone && isCurrent)) && !isPod
}
/// Returns whether the device is an iPad (real or simulator)
public var isPad: Bool {
return isOneOf(Device.allPads)
|| isOneOf(Device.allSimulatorPads)
|| (UIDevice.current.userInterfaceIdiom == .pad && isCurrent)
}
/// Returns whether the device is any of the simulator
/// 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 this device is a simulator return the underlying device,
/// otherwise return `self`.
public var realDevice: Device {
return Device.realDevice(from: self)
}
public var isZoomed: Bool? {
guard isCurrent else { return nil }
if Int(UIScreen.main.scale.rounded()) == 3 {
// Plus-sized
return UIScreen.main.nativeScale > 2.7 && UIScreen.main.nativeScale < 3
} else {
return UIScreen.main.nativeScale > UIScreen.main.scale
}
}
/// All Touch ID Capable Devices
public static var allTouchIDCapableDevices: [Device] {
return [.iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPadMini3, .iPadMini4, .iPadMini5, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch]
}
/// All Face ID Capable Devices
public static var allFaceIDCapableDevices: [Device] {
return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPadPro11Inch, .iPadPro12Inch3]
}
/// Returns whether or not the device has Touch ID
public var isTouchIDCapable: Bool {
return isOneOf(Device.allTouchIDCapableDevices)
}
/// Returns whether or not the device has Face ID
public var isFaceIDCapable: Bool {
return isOneOf(Device.allFaceIDCapableDevices)
}
/// Returns whether or not the device has any biometric sensor (i.e. Touch ID or Face ID)
public var hasBiometricSensor: Bool {
return isTouchIDCapable || isFaceIDCapable
}
/// All devices that feature a sensor housing in the screen
public static var allDevicesWithSensorHousing: [Device] {
return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax]
}
/// Returns whether or not the device has a sensor housing
public var hasSensorHousing: Bool {
return isOneOf(Device.allDevicesWithSensorHousing)
}
/// All devices that feature a screen with rounded corners.
public static var allDevicesWithRoundedDisplayCorners: [Device] {
return [.iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax, .iPadPro11Inch, .iPadPro12Inch3]
}
/// Returns whether or not the device has a screen with rounded corners.
public var hasRoundedDisplayCorners: Bool {
return isOneOf(Device.allDevicesWithRoundedDisplayCorners)
}
/// All devices that have 3D Touch support.
public static var allDevicesWith3dTouchSupport: [Device] {
return [.iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax]
}
/// Returns whether or not the device has 3D Touch support.
public var has3dTouchSupport: Bool {
return isOneOf(Device.allDevicesWith3dTouchSupport)
}
/// All devices that support wireless charging.
public static var allDevicesWithWirelessChargingSupport: [Device] {
return [.iPhone8, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhoneXR, .iPhone11, .iPhone11Pro, .iPhone11ProMax]
}
/// Returns whether or not the device supports wireless charging
public var supportsWirelessCharging: Bool {
return isOneOf(Device.allDevicesWithWirelessChargingSupport)
}
#elseif os(tvOS)
/// All TVs
public static var allTVs: [Device] {
return [.appleTVHD, .appleTV4K]
}
/// All simulator TVs
public static var allSimulatorTVs: [Device] {
return allTVs.map(Device.simulator)
}
#elseif os(watchOS)
/// All Watches
public static var allWatches: [Device] {
return [.appleWatchSeries0_38mm, .appleWatchSeries0_42mm, .appleWatchSeries1_38mm, .appleWatchSeries1_42mm, .appleWatchSeries2_38mm, .appleWatchSeries2_42mm, .appleWatchSeries3_38mm, .appleWatchSeries3_42mm, .appleWatchSeries4_40mm, .appleWatchSeries4_44mm, .appleWatchSeries5_40mm, .appleWatchSeries5_44mm]
}
/// All simulator Watches
public static var allSimulatorWatches: [Device] {
return allWatches.map(Device.simulator)
}
/// All watches that have Force Touch support.
public static var allWatchesWithForceTouchSupport: [Device] {
return [.appleWatchSeries0_38mm, .appleWatchSeries0_42mm, .appleWatchSeries1_38mm, .appleWatchSeries1_42mm, .appleWatchSeries2_38mm, .appleWatchSeries2_42mm, .appleWatchSeries3_38mm, .appleWatchSeries3_42mm, .appleWatchSeries4_40mm, .appleWatchSeries4_44mm, .appleWatchSeries5_40mm, .appleWatchSeries5_44mm]
}
/// Returns whether or not the device has Force Touch support.
public var hasForceTouchSupport: Bool {
return isOneOf(Device.allWatchesWithForceTouchSupport)
}
#endif
/// All real devices (i.e. all devices except for all simulators)
public static var allRealDevices: [Device] {
#if os(iOS)
return allPods + allPhones + allPads
#elseif os(tvOS)
return allTVs
#elseif os(watchOS)
return allWatches
#endif
}
/// All simulators
public static var allSimulators: [Device] {
return allRealDevices.map(Device.simulator)
}
/**
This method saves you in many cases from the need of updating your code with every new device.
Most uses for an enum like this are the following:
```
switch Device.current {
case .iPodTouch5, .iPodTouch6: callMethodOnIPods()
case .iPhone4, iPhone4s, .iPhone5, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX: callMethodOnIPhones()
case .iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadPro: callMethodOnIPads()
default: break
}
```
This code can now be replaced with
```
let device = Device.current
if device.isOneOf(Device.allPods) {
callMethodOnIPods()
} else if device.isOneOf(Device.allPhones) {
callMethodOnIPhones()
} else if device.isOneOf(Device.allPads) {
callMethodOnIPads()
}
```
- parameter devices: An array of devices.
- returns: Returns whether the current device is one of the passed in ones.
*/
public func isOneOf(_ devices: [Device]) -> Bool {
return devices.contains(self)
}
/// Whether or not the current device is the current device.
private var isCurrent: Bool {
return self == Device.current
}
/// The name identifying the device (e.g. "Dennis' iPhone").
public var name: String? {
guard isCurrent else { return nil }
#if os(watchOS)
return WKInterfaceDevice.current().name
#else
return UIDevice.current.name
#endif
}
/// The name of the operating system running on the device represented by the receiver (e.g. "iOS" or "tvOS").
public var systemName: String? {
guard isCurrent else { return nil }
#if os(watchOS)
return WKInterfaceDevice.current().systemName
#else
return UIDevice.current.systemName
#endif
}
/// The current version of the operating system (e.g. 8.4 or 9.2).
public var systemVersion: String? {
guard isCurrent else { return nil }
#if os(watchOS)
return WKInterfaceDevice.current().systemVersion
#else
return UIDevice.current.systemVersion
#endif
}
/// The model of the device (e.g. "iPhone" or "iPod Touch").
public var model: String? {
guard isCurrent else { return nil }
#if os(watchOS)
return WKInterfaceDevice.current().model
#else
return UIDevice.current.model
#endif
}
/// The model of the device as a localized string.
public var localizedModel: String? {
guard isCurrent else { return nil }
#if os(watchOS)
return WKInterfaceDevice.current().localizedModel
#else
return UIDevice.current.localizedModel
#endif
}
/// PPI (Pixels per Inch) on the current device's screen (if applicable). When the device is not applicable this property returns nil.
public var ppi: Int? {
#if os(iOS)
switch self {
case .iPodTouch5: return 326
case .iPodTouch6: return 326
case .iPodTouch7: return 326
case .iPhone4: return 326
case .iPhone4s: return 326
case .iPhone5: return 326
case .iPhone5c: return 326
case .iPhone5s: return 326
case .iPhone6: return 326
case .iPhone6Plus: return 401
case .iPhone6s: return 326
case .iPhone6sPlus: return 401
case .iPhone7: return 326
case .iPhone7Plus: return 401
case .iPhoneSE: return 326
case .iPhone8: return 326
case .iPhone8Plus: return 401
case .iPhoneX: return 458
case .iPhoneXS: return 458
case .iPhoneXSMax: return 458
case .iPhoneXR: return 326
case .iPhone11: return 326
case .iPhone11Pro: return 458
case .iPhone11ProMax: return 458
case .iPad2: return 132
case .iPad3: return 264
case .iPad4: return 264
case .iPadAir: return 264
case .iPadAir2: return 264
case .iPad5: return 264
case .iPad6: return 264
case .iPadAir3: return 264
case .iPad7: return 264
case .iPadMini: return 163
case .iPadMini2: return 326
case .iPadMini3: return 326
case .iPadMini4: return 326
case .iPadMini5: return 326
case .iPadPro9Inch: return 264
case .iPadPro12Inch: return 264
case .iPadPro12Inch2: return 264
case .iPadPro10Inch: return 264
case .iPadPro11Inch: return 264
case .iPadPro12Inch3: return 264
case .homePod: return -1
case .simulator(let model): return model.ppi
case .unknown: return nil
}
#elseif os(watchOS)
switch self {
case .appleWatchSeries0_38mm: return 290
case .appleWatchSeries0_42mm: return 303
case .appleWatchSeries1_38mm: return 290
case .appleWatchSeries1_42mm: return 303
case .appleWatchSeries2_38mm: return 290
case .appleWatchSeries2_42mm: return 303
case .appleWatchSeries3_38mm: return 290
case .appleWatchSeries3_42mm: return 303
case .appleWatchSeries4_40mm: return 326
case .appleWatchSeries4_44mm: return 326
case .appleWatchSeries5_40mm: return 326
case .appleWatchSeries5_44mm: return 326
case .simulator(let model): return model.ppi
case .unknown: return nil
}
#elseif os(tvOS)
return nil
#endif
}
/// True when a Guided Access session is currently active; otherwise, false.
public var isGuidedAccessSessionActive: Bool {
#if os(iOS)
#if swift(>=4.2)
return UIAccessibility.isGuidedAccessEnabled
#else
return UIAccessibilityIsGuidedAccessEnabled()
#endif
#else
return false
#endif
}
/// The brightness level of the screen.
public var screenBrightness: Int {
#if os(iOS)
return Int(UIScreen.main.brightness * 100)
#else
return 100
#endif
}
}
// MARK: - CustomStringConvertible
extension Device: CustomStringConvertible {
/// A textual representation of the device.
public var description: String {
#if os(iOS)
switch self {
case .iPodTouch5: return "iPod touch (5th generation)"
case .iPodTouch6: return "iPod touch (6th generation)"
case .iPodTouch7: return "iPod touch (7th generation)"
case .iPhone4: return "iPhone 4"
case .iPhone4s: return "iPhone 4s"
case .iPhone5: return "iPhone 5"
case .iPhone5c: return "iPhone 5c"
case .iPhone5s: return "iPhone 5s"
case .iPhone6: return "iPhone 6"
case .iPhone6Plus: return "iPhone 6 Plus"
case .iPhone6s: return "iPhone 6s"
case .iPhone6sPlus: return "iPhone 6s Plus"
case .iPhone7: return "iPhone 7"
case .iPhone7Plus: return "iPhone 7 Plus"
case .iPhoneSE: return "iPhone SE"
case .iPhone8: return "iPhone 8"
case .iPhone8Plus: return "iPhone 8 Plus"
case .iPhoneX: return "iPhone X"
case .iPhoneXS: return "iPhone Xs"
case .iPhoneXSMax: return "iPhone Xs Max"
case .iPhoneXR: return "iPhone Xʀ"
case .iPhone11: return "iPhone 11"
case .iPhone11Pro: return "iPhone 11 Pro"
case .iPhone11ProMax: return "iPhone 11 Pro Max"
case .iPad2: return "iPad 2"
case .iPad3: return "iPad (3rd generation)"
case .iPad4: return "iPad (4th generation)"
case .iPadAir: return "iPad Air"
case .iPadAir2: return "iPad Air 2"
case .iPad5: return "iPad (5th generation)"
case .iPad6: return "iPad (6th generation)"
case .iPadAir3: return "iPad Air (3rd generation)"
case .iPad7: return "iPad (7th generation)"
case .iPadMini: return "iPad Mini"
case .iPadMini2: return "iPad Mini 2"
case .iPadMini3: return "iPad Mini 3"
case .iPadMini4: return "iPad Mini 4"
case .iPadMini5: return "iPad Mini (5th generation)"
case .iPadPro9Inch: return "iPad Pro (9.7-inch)"
case .iPadPro12Inch: return "iPad Pro (12.9-inch)"
case .iPadPro12Inch2: return "iPad Pro (12.9-inch) (2nd generation)"
case .iPadPro10Inch: return "iPad Pro (10.5-inch)"
case .iPadPro11Inch: return "iPad Pro (11-inch)"
case .iPadPro12Inch3: return "iPad Pro (12.9-inch) (3rd generation)"
case .homePod: return "HomePod"
case .simulator(let model): return "Simulator (\(model))"
case .unknown(let identifier): return identifier
}
#elseif os(watchOS)
switch self {
case .appleWatchSeries0_38mm: return "Apple Watch (1st generation) 38mm"
case .appleWatchSeries0_42mm: return "Apple Watch (1st generation) 42mm"
case .appleWatchSeries1_38mm: return "Apple Watch Series 1 38mm"
case .appleWatchSeries1_42mm: return "Apple Watch Series 1 42mm"
case .appleWatchSeries2_38mm: return "Apple Watch Series 2 38mm"
case .appleWatchSeries2_42mm: return "Apple Watch Series 2 42mm"
case .appleWatchSeries3_38mm: return "Apple Watch Series 3 38mm"
case .appleWatchSeries3_42mm: return "Apple Watch Series 3 42mm"
case .appleWatchSeries4_40mm: return "Apple Watch Series 4 40mm"
case .appleWatchSeries4_44mm: return "Apple Watch Series 4 44mm"
case .appleWatchSeries5_40mm: return "Apple Watch Series 5 40mm"
case .appleWatchSeries5_44mm: return "Apple Watch Series 5 44mm"
case .simulator(let model): return "Simulator (\(model))"
case .unknown(let identifier): return identifier
}
#elseif os(tvOS)
switch self {
case .appleTVHD: return "Apple TV HD"
case .appleTV4K: return "Apple TV 4K"
case .simulator(let model): return "Simulator (\(model))"
case .unknown(let identifier): return identifier
}
#endif
}
}
// MARK: - Equatable
extension Device: Equatable {
/// Compares two devices
///
/// - parameter lhs: A device.
/// - parameter rhs: Another device.
///
/// - returns: `true` iff the underlying identifier is the same.
public static func == (lhs: Device, rhs: Device) -> Bool {
return lhs.description == rhs.description
}
}
// MARK: - Battery
#if os(iOS) || os(watchOS)
@available(iOS 8.0, watchOS 4.0, *)
extension Device {
/**
This enum describes the state of the battery.
- Full: The device is plugged into power and the battery is 100% charged or the device is the iOS Simulator.
- Charging: The device is plugged into power and the battery is less than 100% charged.
- Unplugged: The device is not plugged into power; the battery is discharging.
*/
public enum BatteryState: CustomStringConvertible, Equatable {
/// The device is plugged into power and the battery is 100% charged or the device is the iOS Simulator.
case full
/// The device is plugged into power and the battery is less than 100% charged.
/// The associated value is in percent (0-100).
case charging(Int)
/// The device is not plugged into power; the battery is discharging.
/// The associated value is in percent (0-100).
case unplugged(Int)
#if os(iOS)
fileprivate init() {
let wasBatteryMonitoringEnabled = UIDevice.current.isBatteryMonitoringEnabled
UIDevice.current.isBatteryMonitoringEnabled = true
let batteryLevel = Int(round(UIDevice.current.batteryLevel * 100)) // round() is actually not needed anymore since -[batteryLevel] seems to always return a two-digit precision number
// but maybe that changes in the future.
switch UIDevice.current.batteryState {
case .charging: self = .charging(batteryLevel)
case .full: self = .full
case .unplugged: self = .unplugged(batteryLevel)
case .unknown: self = .full // Should never happen since `batteryMonitoring` is enabled.
@unknown default:
self = .full // To cover any future additions for which DeviceKit might not have updated yet.
}
UIDevice.current.isBatteryMonitoringEnabled = wasBatteryMonitoringEnabled
}
#elseif os(watchOS)
fileprivate init() {
let wasBatteryMonitoringEnabled = WKInterfaceDevice.current().isBatteryMonitoringEnabled
WKInterfaceDevice.current().isBatteryMonitoringEnabled = true
let batteryLevel = Int(round(WKInterfaceDevice.current().batteryLevel * 100)) // round() is actually not needed anymore since -[batteryLevel] seems to always return a two-digit precision number
// but maybe that changes in the future.
switch WKInterfaceDevice.current().batteryState {
case .charging: self = .charging(batteryLevel)
case .full: self = .full
case .unplugged: self = .unplugged(batteryLevel)
case .unknown: self = .full // Should never happen since `batteryMonitoring` is enabled.
@unknown default:
self = .full // To cover any future additions for which DeviceKit might not have updated yet.
}
WKInterfaceDevice.current().isBatteryMonitoringEnabled = wasBatteryMonitoringEnabled
}
#endif
/// The user enabled Low Power mode
public var lowPowerMode: Bool {
return false
}
/// Provides a textual representation of the battery state.
/// Examples:
/// ```
/// Battery level: 90%, device is plugged in.
/// Battery level: 100 % (Full), device is plugged in.
/// Battery level: \(batteryLevel)%, device is unplugged.
/// ```
public var description: String {
switch self {
case .charging(let batteryLevel): return "Battery level: \(batteryLevel)%, device is plugged in."
case .full: return "Battery level: 100 % (Full), device is plugged in."
case .unplugged(let batteryLevel): return "Battery level: \(batteryLevel)%, device is unplugged."
}
}
}
/// The state of the battery
public var batteryState: BatteryState? {
guard isCurrent else { return nil }
return BatteryState()
}
/// Battery level ranges from 0 (fully discharged) to 100 (100% charged).
public var batteryLevel: Int? {
guard isCurrent else { return nil }
switch BatteryState() {
case .charging(let value): return value
case .full: return 100
case .unplugged(let value): return value
}
}
}
#endif
// MARK: - Device.Batterystate: Comparable
#if os(iOS) || os(watchOS)
@available(iOS 8.0, watchOS 4.0, *)
extension Device.BatteryState: Comparable {
/// Tells if two battery states are equal.
///
/// - parameter lhs: A battery state.
/// - parameter rhs: Another battery state.
///
/// - returns: `true` iff they are equal, otherwise `false`
public static func == (lhs: Device.BatteryState, rhs: Device.BatteryState) -> Bool {
return lhs.description == rhs.description
}
/// Compares two battery states.
///
/// - parameter lhs: A battery state.
/// - parameter rhs: Another battery state.
///
/// - returns: `true` if rhs is `.Full`, `false` when lhs is `.Full` otherwise their battery level is compared.
public static func < (lhs: Device.BatteryState, rhs: Device.BatteryState) -> Bool {
switch (lhs, rhs) {
case (.full, _): return false // return false (even if both are `.Full` -> they are equal)
case (_, .full): return true // lhs is *not* `.Full`, rhs is
case let (.charging(lhsLevel), .charging(rhsLevel)): return lhsLevel < rhsLevel
case let (.charging(lhsLevel), .unplugged(rhsLevel)): return lhsLevel < rhsLevel
case let (.unplugged(lhsLevel), .charging(rhsLevel)): return lhsLevel < rhsLevel
case let (.unplugged(lhsLevel), .unplugged(rhsLevel)): return lhsLevel < rhsLevel
default: return false // compiler won't compile without it, though it cannot happen
}
}
}
#endif
#if os(iOS)
extension Device {
// MARK: - Orientation
/**
This enum describes the state of the orientation.
- Landscape: The device is in Landscape Orientation
- Portrait: The device is in Portrait Orientation
*/
public enum Orientation {
case landscape
case portrait
}
public var orientation: Orientation {
if UIDevice.current.orientation.isLandscape {
return .landscape
} else {
return .portrait
}
}
}
#endif
#if os(iOS)
// MARK: - DiskSpace
extension Device {
/// Return the root url
///
/// - returns: the NSHomeDirectory() url
private static let rootURL = URL(fileURLWithPath: NSHomeDirectory())
/// The volume’s total capacity in bytes.
public static var volumeTotalCapacity: Int? {
return (try? Device.rootURL.resourceValues(forKeys: [.volumeTotalCapacityKey]))?.volumeTotalCapacity
}
/// The volume’s available capacity in bytes.
public static var volumeAvailableCapacity: Int? {
return (try? rootURL.resourceValues(forKeys: [.volumeAvailableCapacityKey]))?.volumeAvailableCapacity
}
/// The volume’s available capacity in bytes for storing important resources.
@available(iOS 11.0, *)
public static var volumeAvailableCapacityForImportantUsage: Int64? {
return (try? rootURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey]))?.volumeAvailableCapacityForImportantUsage
}
/// The volume’s available capacity in bytes for storing nonessential resources.
@available(iOS 11.0, *)
public static var volumeAvailableCapacityForOpportunisticUsage: Int64? { //swiftlint:disable:this identifier_name
return (try? rootURL.resourceValues(forKeys: [.volumeAvailableCapacityForOpportunisticUsageKey]))?.volumeAvailableCapacityForOpportunisticUsage
}
/// All volumes capacity information in bytes.
@available(iOS 11.0, *)
public static var volumes: [URLResourceKey: Int64]? {
do {
let values = try rootURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey,
.volumeAvailableCapacityKey,
.volumeAvailableCapacityForOpportunisticUsageKey,
.volumeTotalCapacityKey
])
return values.allValues.mapValues {
if let int = $0 as? Int64 {
return int
}
if let int = $0 as? Int {
return Int64(int)
}
return 0
}
} catch {
return nil
}
}
}
#endif
#if os(iOS)
// MARK: - Apple Pencil
extension Device {
/**
This option set describes the current Apple Pencils
- firstGeneration: 1st Generation Apple Pencil
- secondGeneration: 2nd Generation Apple Pencil
*/
public struct ApplePencilSupport: OptionSet {
public var rawValue: UInt
public init(rawValue: UInt) {
self.rawValue = rawValue
}
public static let firstGeneration = ApplePencilSupport(rawValue: 0x01)
public static let secondGeneration = ApplePencilSupport(rawValue: 0x02)
}
/// All Apple Pencil Capable Devices
public static var allApplePencilCapableDevices: [Device] {
return [.iPad6, .iPadAir3, .iPad7, .iPadMini5, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3]
}
/// Returns supported version of the Apple Pencil
public var applePencilSupport: ApplePencilSupport {
switch self {
case .iPad6: return .firstGeneration
case .iPadAir3: return .firstGeneration
case .iPad7: return .firstGeneration
case .iPadMini5: return .firstGeneration
case .iPadPro9Inch: return .firstGeneration
case .iPadPro12Inch: return .firstGeneration
case .iPadPro12Inch2: return .firstGeneration
case .iPadPro10Inch: return .firstGeneration
case .iPadPro11Inch: return .secondGeneration
case .iPadPro12Inch3: return .secondGeneration
default: return []
}
}
}
#endif
#if os(iOS)
// MARK: - Cameras
extension Device {
public enum CameraTypes {
case normal
case telephoto
case ultraWide
}
/// Returns an array of the types of cameras the device has
public var cameras: [CameraTypes] {
switch self {
case .iPodTouch5: return [.normal]
case .iPodTouch6: return [.normal]
case .iPodTouch7: return [.normal]
case .iPhone4: return [.normal]
case .iPhone4s: return [.normal]
case .iPhone5: return [.normal]
case .iPhone5c: return [.normal]
case .iPhone5s: return [.normal]
case .iPhone6: return [.normal]
case .iPhone6Plus: return [.normal]
case .iPhone6s: return [.normal]
case .iPhone6sPlus: return [.normal]
case .iPhone7: return [.normal]
case .iPhoneSE: return [.normal]
case .iPhone8: return [.normal]
case .iPhoneXR: return [.normal]
case .iPad2: return [.normal]
case .iPad3: return [.normal]
case .iPad4: return [.normal]
case .iPadAir: return [.normal]
case .iPadAir2: return [.normal]
case .iPad5: return [.normal]
case .iPad6: return [.normal]
case .iPadAir3: return [.normal]
case .iPad7: return [.normal]
case .iPadMini: return [.normal]
case .iPadMini2: return [.normal]
case .iPadMini3: return [.normal]
case .iPadMini4: return [.normal]
case .iPadMini5: return [.normal]
case .iPadPro9Inch: return [.normal]
case .iPadPro12Inch: return [.normal]
case .iPadPro12Inch2: return [.normal]
case .iPadPro10Inch: return [.normal]
case .iPadPro11Inch: return [.normal]
case .iPadPro12Inch3: return [.normal]
case .iPhone7Plus: return [.normal, .telephoto]
case .iPhone8Plus: return [.normal, .telephoto]
case .iPhoneX: return [.normal, .telephoto]
case .iPhoneXS: return [.normal, .telephoto]
case .iPhoneXSMax: return [.normal, .telephoto]
case .iPhone11: return [.normal, .ultraWide]
case .iPhone11Pro: return [.normal, .telephoto, .ultraWide]
case .iPhone11ProMax: return [.normal, .telephoto, .ultraWide]
default: return []
}
}
/// 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, .iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3]
}
/// All devices that feature a normal camera
public static var allDevicesWithNormalCamera: [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, .iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPad6, .iPadAir3, .iPad7, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadMini5, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch, .iPadPro11Inch, .iPadPro12Inch3]
}
/// All devices that feature a telephoto camera
public static var allDevicesWithTelephotoCamera: [Device] {
return [.iPhone7Plus, .iPhone8Plus, .iPhoneX, .iPhoneXS, .iPhoneXSMax, .iPhone11Pro, .iPhone11ProMax]
}
/// All devices that feature an ultra wide camera
public static var allDevicesWithUltraWideCamera: [Device] {
return [.iPhone11, .iPhone11Pro, .iPhone11ProMax]
}
/// Returns whether or not the current device has a camera
public var hasCamera: Bool {
return !self.cameras.isEmpty
}
/// Returns whether or not the current device has a normal camera
public var hasNormalCamera: Bool {
return self.cameras.contains(.normal)
}
/// Returns whether or not the current device has a telephoto camera
public var hasTelephotoCamera: Bool {
return self.cameras.contains(.telephoto)
}
/// Returns whether or not the current device has an ultra wide camera
public var hasUltraWideCamera: Bool {
return self.cameras.contains(.ultraWide)
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment