mirror of
https://github.com/devicekit/DeviceKit.git
synced 2026-06-16 10:24:30 +00:00
687 lines
26 KiB
Swift
687 lines
26 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the DeviceKit open source project
|
|
//
|
|
// Copyright © 2014 - 2017 Dennis Weissmann and the DeviceKit project authors
|
|
//
|
|
// License: https://github.com/dennisweissmann/DeviceKit/blob/master/LICENSE
|
|
// Contributors: https://github.com/dennisweissmann/DeviceKit#contributors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import UIKit
|
|
|
|
// MARK: - Device
|
|
|
|
/// This enum is a value-type wrapper and extension of
|
|
/// [`UIDevice`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/).
|
|
///
|
|
/// Usage:
|
|
///
|
|
/// let device = Device()
|
|
///
|
|
/// 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)
|
|
/// Device is an [iPod Touch (5th generation)](https://support.apple.com/kb/SP657)
|
|
///
|
|
/// 
|
|
case iPodTouch5
|
|
/// Device is an [iPod Touch (6th generation)](https://support.apple.com/kb/SP720)
|
|
///
|
|
/// 
|
|
case iPodTouch6
|
|
/// Device is an [iPhone 4](https://support.apple.com/kb/SP587)
|
|
///
|
|
/// 
|
|
case iPhone4
|
|
/// Device is an [iPhone 4s](https://support.apple.com/kb/SP643)
|
|
///
|
|
/// 
|
|
case iPhone4s
|
|
/// Device is an [iPhone 5](https://support.apple.com/kb/SP655)
|
|
///
|
|
/// 
|
|
case iPhone5
|
|
/// Device is an [iPhone 5c](https://support.apple.com/kb/SP684)
|
|
///
|
|
/// 
|
|
case iPhone5c
|
|
/// Device is an [iPhone 5s](https://support.apple.com/kb/SP685)
|
|
///
|
|
/// 
|
|
case iPhone5s
|
|
/// Device is an [iPhone 6](https://support.apple.com/kb/SP705)
|
|
///
|
|
/// 
|
|
case iPhone6
|
|
/// Device is an [iPhone 6 Plus](https://support.apple.com/kb/SP706)
|
|
///
|
|
/// 
|
|
case iPhone6Plus
|
|
/// Device is an [iPhone 6s](https://support.apple.com/kb/SP726)
|
|
///
|
|
/// 
|
|
case iPhone6s
|
|
/// Device is an [iPhone 6s Plus](https://support.apple.com/kb/SP727)
|
|
///
|
|
/// 
|
|
case iPhone6sPlus
|
|
/// Device is an [iPhone 7](https://support.apple.com/kb/SP743)
|
|
///
|
|
/// 
|
|
case iPhone7
|
|
/// Device is an [iPhone 7 Plus](https://support.apple.com/kb/SP744)
|
|
///
|
|
/// 
|
|
case iPhone7Plus
|
|
/// Device is an [iPhone SE](https://support.apple.com/kb/SP738)
|
|
///
|
|
/// 
|
|
case iPhoneSE
|
|
/// Device is an [iPhone 8](https://support.apple.com/kb/SP767)
|
|
///
|
|
/// 
|
|
case iPhone8
|
|
/// Device is an [iPhone 8 Plus](https://support.apple.com/kb/SP768)
|
|
///
|
|
/// 
|
|
case iPhone8Plus
|
|
/// Device is an [iPhone X](https://support.apple.com/kb/SP770)
|
|
///
|
|
/// 
|
|
case iPhoneX
|
|
/// Device is an [iPad 2](https://support.apple.com/kb/SP622)
|
|
///
|
|
/// 
|
|
case iPad2
|
|
/// Device is an [iPad (3rd generation)](https://support.apple.com/kb/SP647)
|
|
///
|
|
/// 
|
|
case iPad3
|
|
/// Device is an [iPad (4th generation)](https://support.apple.com/kb/SP662)
|
|
///
|
|
/// 
|
|
case iPad4
|
|
/// Device is an [iPad Air](https://support.apple.com/kb/SP692)
|
|
///
|
|
/// 
|
|
case iPadAir
|
|
/// Device is an [iPad Air 2](https://support.apple.com/kb/SP708)
|
|
///
|
|
/// 
|
|
case iPadAir2
|
|
/// Device is an [iPad 5](https://support.apple.com/kb/SP751)
|
|
///
|
|
/// 
|
|
case iPad5
|
|
/// Device is an [iPad Mini](https://support.apple.com/kb/SP661)
|
|
///
|
|
/// 
|
|
case iPadMini
|
|
/// Device is an [iPad Mini 2](https://support.apple.com/kb/SP693)
|
|
///
|
|
/// 
|
|
case iPadMini2
|
|
/// Device is an [iPad Mini 3](https://support.apple.com/kb/SP709)
|
|
///
|
|
/// 
|
|
case iPadMini3
|
|
/// Device is an [iPad Mini 4](https://support.apple.com/kb/SP725)
|
|
///
|
|
/// 
|
|
case iPadMini4
|
|
/// Device is an [iPad Pro](https://support.apple.com/kb/SP739)
|
|
///
|
|
/// 
|
|
case iPadPro9Inch
|
|
/// Device is an [iPad Pro](https://support.apple.com/kb/sp723)
|
|
///
|
|
/// 
|
|
case iPadPro12Inch
|
|
/// Device is an [iPad Pro](https://support.apple.com/kb/SP761)
|
|
///
|
|
/// 
|
|
case iPadPro12Inch2
|
|
/// Device is an [iPad Pro 10.5](https://support.apple.com/kb/SP762)
|
|
///
|
|
/// 
|
|
case iPadPro10Inch
|
|
#elseif os(tvOS)
|
|
/// Device is an [Apple TV](http://www.apple.com/tv/)
|
|
///
|
|
/// 
|
|
case appleTV4
|
|
#endif
|
|
|
|
/// Device is [Simulator](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/iOS_Simulator_Guide/Introduction/Introduction.html)
|
|
///
|
|
/// 
|
|
indirect case simulator(Device)
|
|
|
|
/// Device is not yet known (implemented)
|
|
/// You can still use this enum as before but the description equals the identifier (you can get multiple identifiers for the same product class
|
|
/// (e.g. "iPhone6,1" or "iPhone 6,2" do both mean "iPhone 5s"))
|
|
case unknown(String)
|
|
|
|
/// Initializes a `Device` representing the current device this software runs on.
|
|
public init() {
|
|
self = 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
|
|
#if os(iOS)
|
|
switch identifier {
|
|
case "iPod5,1": return iPodTouch5
|
|
case "iPod7,1": return iPodTouch6
|
|
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,4": return iPhone8
|
|
case "iPhone10,5": return iPhone8Plus
|
|
case "iPhone10,3": return iPhoneX
|
|
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 "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 "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 "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 appleTV4
|
|
case "i386", "x86_64": return simulator(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))
|
|
default: return unknown(identifier)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if os(iOS)
|
|
/// All iPods
|
|
public static var allPods: [Device] {
|
|
return [.iPodTouch5, .iPodTouch6]
|
|
}
|
|
|
|
/// All iPhones
|
|
public static var allPhones: [Device] {
|
|
return [.iPhone4, .iPhone4s, .iPhone5, .iPhone5c, .iPhone5s, .iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .iPhone7, .iPhone7Plus, .iPhoneSE, .iPhone8, .iPhone8Plus, .iPhoneX]
|
|
}
|
|
|
|
/// All iPads
|
|
public static var allPads: [Device] {
|
|
return [.iPad2, .iPad3, .iPad4, .iPadAir, .iPadAir2, .iPad5, .iPadMini, .iPadMini2, .iPadMini3, .iPadMini4, .iPadPro9Inch, .iPadPro12Inch, .iPadPro12Inch2, .iPadPro10Inch]
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
|
|
public var isZoomed: Bool {
|
|
if Int(UIScreen.main.scale.rounded()) == 3 {
|
|
// Plus-sized
|
|
return UIScreen.main.nativeScale > 2.7
|
|
} else {
|
|
return UIScreen.main.nativeScale > UIScreen.main.scale
|
|
}
|
|
}
|
|
|
|
/// Returns diagonal screen length in inches
|
|
public var diagonal: Double {
|
|
switch self {
|
|
case .iPodTouch5: return 4
|
|
case .iPodTouch6: 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 .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 .iPadMini: return 7.9
|
|
case .iPadMini2: return 7.9
|
|
case .iPadMini3: return 7.9
|
|
case .iPadMini4: return 7.9
|
|
case .iPadPro9Inch: return 9.7
|
|
case .iPadPro12Inch: return 12.9
|
|
case .iPadPro12Inch2: return 12.9
|
|
case .iPadPro10Inch: return 10.5
|
|
case .simulator(let model): return model.diagonal
|
|
case .unknown: return -1
|
|
}
|
|
}
|
|
|
|
/// Returns screen ratio as a tuple
|
|
public var screenRatio: (width: Double, height: Double) {
|
|
switch self {
|
|
case .iPodTouch5: return (width: 9, height: 16)
|
|
case .iPodTouch6: 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 .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 .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 .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 .simulator(let model): return model.screenRatio
|
|
case .unknown: return (width: -1, height: -1)
|
|
}
|
|
}
|
|
#elseif os(tvOS)
|
|
/// All TVs
|
|
public static var allTVs: [Device] {
|
|
return [.appleTV4]
|
|
}
|
|
|
|
/// All simulator TVs
|
|
public static var allSimulatorTVs: [Device] {
|
|
return allTVs.map(Device.simulator)
|
|
}
|
|
#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
|
|
#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() {
|
|
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()
|
|
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)
|
|
}
|
|
|
|
/// The name identifying the device (e.g. "Dennis' iPhone").
|
|
public var name: String {
|
|
return UIDevice.current.name
|
|
}
|
|
|
|
/// The name of the operating system running on the device represented by the receiver (e.g. "iOS" or "tvOS").
|
|
public var systemName: String {
|
|
return UIDevice.current.systemName
|
|
}
|
|
|
|
/// The current version of the operating system (e.g. 8.4 or 9.2).
|
|
public var systemVersion: String {
|
|
return UIDevice.current.systemVersion
|
|
}
|
|
|
|
/// The model of the device (e.g. "iPhone" or "iPod Touch").
|
|
public var model: String {
|
|
return UIDevice.current.model
|
|
}
|
|
|
|
/// The model of the device as a localized string.
|
|
public var localizedModel: String {
|
|
return UIDevice.current.localizedModel
|
|
}
|
|
|
|
/// 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 .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 .iPad2: return 132
|
|
case .iPad3: return 264
|
|
case .iPad4: return 264
|
|
case .iPadAir: return 264
|
|
case .iPadAir2: return 264
|
|
case .iPad5: return 264
|
|
case .iPadMini: return 163
|
|
case .iPadMini2: return 326
|
|
case .iPadMini3: return 326
|
|
case .iPadMini4: return 326
|
|
case .iPadPro9Inch: return 264
|
|
case .iPadPro12Inch: return 264
|
|
case .iPadPro12Inch2: return 264
|
|
case .iPadPro10Inch: return 264
|
|
case .simulator(let model): return model.ppi
|
|
case .unknown: return nil
|
|
}
|
|
#elseif os(tvOS)
|
|
return nil
|
|
#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 5"
|
|
case .iPodTouch6: return "iPod Touch 6"
|
|
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 .iPad2: return "iPad 2"
|
|
case .iPad3: return "iPad 3"
|
|
case .iPad4: return "iPad 4"
|
|
case .iPadAir: return "iPad Air"
|
|
case .iPadAir2: return "iPad Air 2"
|
|
case .iPad5: return "iPad 5"
|
|
case .iPadMini: return "iPad Mini"
|
|
case .iPadMini2: return "iPad Mini 2"
|
|
case .iPadMini3: return "iPad Mini 3"
|
|
case .iPadMini4: return "iPad Mini 4"
|
|
case .iPadPro9Inch: return "iPad Pro (9.7-inch)"
|
|
case .iPadPro12Inch: return "iPad Pro (12.9-inch)"
|
|
case .iPadPro12Inch2: return "iPad Pro (12.9-inch) 2"
|
|
case .iPadPro10Inch: return "iPad Pro (10.5-inch)"
|
|
case .simulator(let model): return "Simulator (\(model))"
|
|
case .unknown(let identifier): return identifier
|
|
}
|
|
#elseif os(tvOS)
|
|
switch self {
|
|
case .appleTV4: return "Apple TV 4"
|
|
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
|
|
}
|
|
|
|
}
|
|
|
|
#if os(iOS)
|
|
// MARK: - Battery
|
|
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)
|
|
|
|
fileprivate init() {
|
|
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.
|
|
}
|
|
UIDevice.current.isBatteryMonitoringEnabled = 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 {
|
|
return BatteryState()
|
|
}
|
|
|
|
/// Battery level ranges from 0 (fully discharged) to 100 (100% charged).
|
|
public var batteryLevel: Int {
|
|
switch BatteryState() {
|
|
case .charging(let value): return value
|
|
case .full: return 100
|
|
case .unplugged(let value): return value
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Device.Batterystate: Comparable
|
|
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 (.charging(let lhsLevel), .charging(let rhsLevel)): return lhsLevel < rhsLevel
|
|
case (.charging(let lhsLevel), .unplugged(let rhsLevel)): return lhsLevel < rhsLevel
|
|
case (.unplugged(let lhsLevel), .charging(let rhsLevel)): return lhsLevel < rhsLevel
|
|
case (.unplugged(let lhsLevel), .unplugged(let rhsLevel)): return lhsLevel < rhsLevel
|
|
default: return false // compiler won't compile without it, though it cannot happen
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|