import UIKit public enum DeviceType { case phone case tablet } public enum DeviceMetrics: CaseIterable, Equatable { public struct Performance { public let isGraphicallyCapable: Bool init() { var length: Int = 4 var cpuCount: UInt32 = 0 sysctlbyname("hw.ncpu", &cpuCount, &length, nil, 0) self.isGraphicallyCapable = cpuCount >= 4 } } case iPhone4 case iPhone5 case iPhone6 case iPhone6Plus case iPhoneX case iPhoneXSMax case iPhoneXr case iPhone12Mini case iPhone12 case iPhone12ProMax case iPhone13Mini case iPhone13 case iPhone13Pro case iPhone13ProMax case iPhone14Pro case iPhone14ProZoomed case iPhone14ProMax case iPhone14ProMaxZoomed case iPhone16Pro case iPhone16ProMax case iPad case iPadMini case iPad102Inch case iPadPro10Inch case iPadPro11Inch case iPadPro case iPadPro3rdGen case iPadMini6thGen case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?, screenCornerRadius: CGFloat) public static let performance = Performance() public static var allCases: [DeviceMetrics] { return [ .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax, .iPad, .iPadMini, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini6thGen ] } public init(screenSize: CGSize, scale: CGFloat, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?) { var screenSize = screenSize if screenSize.width > screenSize.height { screenSize = CGSize(width: screenSize.height, height: screenSize.width) } let additionalSize = CGSize(width: screenSize.width, height: screenSize.height + 20.0) for device in DeviceMetrics.allCases { if let _ = onScreenNavigationHeight, device.onScreenNavigationHeight(inLandscape: false, systemOnScreenNavigationHeight: nil) == nil { if case .tablet = device.type { if screenSize.height == 1024.0 && screenSize.width == 768.0 { } else { continue } } else { continue } } let width = device.screenSize.width let height = device.screenSize.height if ((screenSize.width.isEqual(to: width) && screenSize.height.isEqual(to: height)) || (additionalSize.width.isEqual(to: width) && additionalSize.height.isEqual(to: height))) { if case .iPhoneX = device, statusBarHeight == 47.0 { self = .iPhone14ProMaxZoomed } else if case .iPhoneXSMax = device, scale == 2.0 { self = .iPhoneXr } else { self = device } return } } let screenCornerRadius: CGFloat if screenSize.width >= 1024.0 || screenSize.height >= 1024.0 { screenCornerRadius = 0.0 } else if onScreenNavigationHeight != nil { screenCornerRadius = 39.0 } else { screenCornerRadius = 0.0 } self = .unknown(screenSize: screenSize, statusBarHeight: statusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight, screenCornerRadius: screenCornerRadius) } public var type: DeviceType { switch self { case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen: return .tablet case let .unknown(screenSize, _, _, _) where screenSize.width >= 744.0 && screenSize.height >= 1024.0: return .tablet default: return .phone } } public var screenSize: CGSize { switch self { case .iPhone4: return CGSize(width: 320.0, height: 480.0) case .iPhone5: return CGSize(width: 320.0, height: 568.0) case .iPhone6: return CGSize(width: 375.0, height: 667.0) case .iPhone6Plus: return CGSize(width: 414.0, height: 736.0) case .iPhoneX: return CGSize(width: 375.0, height: 812.0) case .iPhoneXSMax, .iPhoneXr: return CGSize(width: 414.0, height: 896.0) case .iPhone12Mini: return CGSize(width: 360.0, height: 780.0) case .iPhone12: return CGSize(width: 390.0, height: 844.0) case .iPhone12ProMax: return CGSize(width: 428.0, height: 926.0) case .iPhone13Mini: return CGSize(width: 375.0, height: 812.0) case .iPhone13: return CGSize(width: 390.0, height: 844.0) case .iPhone13Pro: return CGSize(width: 390.0, height: 844.0) case .iPhone13ProMax: return CGSize(width: 428.0, height: 926.0) case .iPhone14Pro: return CGSize(width: 393.0, height: 852.0) case .iPhone14ProZoomed: return CGSize(width: 320.0, height: 693.0) case .iPhone14ProMax: return CGSize(width: 430.0, height: 932.0) case .iPhone14ProMaxZoomed: return CGSize(width: 375.0, height: 812.0) case .iPhone16Pro: return CGSize(width: 402.0, height: 874.0) case .iPhone16ProMax: return CGSize(width: 440.0, height: 956.0) case .iPad: return CGSize(width: 768.0, height: 1024.0) case .iPadMini: return CGSize(width: 744.0, height: 1133.0) case .iPad102Inch: return CGSize(width: 810.0, height: 1080.0) case .iPadPro10Inch: return CGSize(width: 834.0, height: 1112.0) case .iPadPro11Inch: return CGSize(width: 834.0, height: 1194.0) case .iPadPro, .iPadPro3rdGen: return CGSize(width: 1024.0, height: 1366.0) case .iPadMini6thGen: return CGSize(width: 744.0, height: 1133.0) case let .unknown(screenSize, _, _, _): return screenSize } } public var screenCornerRadius: CGFloat { switch self { case .iPhoneX, .iPhoneXSMax: return 39.0 case .iPhoneXr: return 41.5 case .iPhone12Mini: return 44.0 case .iPhone12, .iPhone13, .iPhone13Pro, .iPhone14ProZoomed: return 47.0 + UIScreenPixel case .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMaxZoomed: return 53.0 + UIScreenPixel case .iPhone14Pro, .iPhone14ProMax: return 55.0 case .iPhone16Pro, .iPhone16ProMax: return 55.0 case let .unknown(_, _, _, screenCornerRadius): return screenCornerRadius default: return 0.0 } } func safeInsets(inLandscape: Bool) -> UIEdgeInsets { switch self { case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone14ProZoomed, .iPhone14ProMaxZoomed: return inLandscape ? UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) : UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0) case .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax: return inLandscape ? UIEdgeInsets(top: 0.0, left: 47.0, bottom: 0.0, right: 47.0) : UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0) case .iPhone14Pro, .iPhone14ProMax, .iPhone16Pro, .iPhone16ProMax: return inLandscape ? UIEdgeInsets(top: 0.0, left: 59.0, bottom: 0.0, right: 59.0) : UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0) default: return UIEdgeInsets.zero } } public func onScreenNavigationHeight(inLandscape: Bool, systemOnScreenNavigationHeight: CGFloat?) -> CGFloat? { switch self { case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax, .iPhone16Pro, .iPhone16ProMax: return inLandscape ? 21.0 : 34.0 case .iPhone14ProZoomed: return inLandscape ? 21.0 : 28.0 case .iPhone14ProMaxZoomed: return inLandscape ? 21.0 : 31.0 case .iPadPro3rdGen, .iPadPro11Inch: return 21.0 case .iPad, .iPadPro, .iPadPro10Inch, .iPadMini, .iPadMini6thGen: if let systemOnScreenNavigationHeight = systemOnScreenNavigationHeight, !systemOnScreenNavigationHeight.isZero { return 21.0 } else { return nil } case let .unknown(_, _, onScreenNavigationHeight, _): return onScreenNavigationHeight default: return nil } } func statusBarHeight(for size: CGSize) -> CGFloat? { let value = self.statusBarHeight if self.type == .tablet { return value } else { if size.width < size.height { return value } else { return nil } } } var statusBarHeight: CGFloat { switch self { case .iPhone14Pro, .iPhone14ProMax: return 54.0 case .iPhone14ProMaxZoomed: return 47.0 case .iPhone16Pro, .iPhone16ProMax: return 54.0 case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax: return 44.0 case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen: return 24.0 case let .unknown(_, statusBarHeight, _, _): return statusBarHeight default: return 20.0 } } public func keyboardHeight(inLandscape: Bool) -> CGFloat { if inLandscape { switch self { case .iPhone4, .iPhone5: return 162.0 case .iPhone6, .iPhone6Plus: return 163.0 case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax: return 172.0 case .iPad, .iPad102Inch, .iPadPro10Inch: return 348.0 case .iPadPro11Inch, .iPadMini, .iPadMini6thGen: return 368.0 case .iPadPro: return 421.0 case .iPadPro3rdGen: return 441.0 case .unknown: return 216.0 } } else { switch self { case .iPhone4, .iPhone5, .iPhone6: return 216.0 case .iPhone6Plus: return 226.0 case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed, .iPhone16Pro: return 292.0 case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax, .iPhone16ProMax: return 302.0 case .iPad, .iPad102Inch, .iPadPro10Inch: return 263.0 case .iPadPro11Inch: return 283.0 case .iPadPro, .iPadMini, .iPadMini6thGen: return 328.0 case .iPadPro3rdGen: return 348.0 case .unknown: return 216.0 } } } func predictiveInputHeight(inLandscape: Bool) -> CGFloat { if inLandscape { switch self { case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax: return 37.0 case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen: return 50.0 case .unknown: return 37.0 } } else { switch self { case .iPhone4, .iPhone5: return 37.0 case .iPhone6, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax: return 44.0 case .iPhone6Plus: return 45.0 case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen: return 50.0 case .unknown: return 44.0 } } } public func standardInputHeight(inLandscape: Bool) -> CGFloat { return self.keyboardHeight(inLandscape: inLandscape) + predictiveInputHeight(inLandscape: inLandscape) } public var hasTopNotch: Bool { switch self { case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax: return true default: return false } } public var hasDynamicIsland: Bool { switch self { case .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed, .iPhone16Pro, .iPhone16ProMax: return true default: return false } } public var showAppBadge: Bool { if case .iPhoneX = self { return false } // MARK: Swiftgram return self.hasTopNotch || self.hasDynamicIsland } } // MARK: Swifgram public extension DeviceMetrics { var deviceModelCode: String { var systemInfo = utsname() uname(&systemInfo) let modelCode = withUnsafePointer(to: &systemInfo.machine) { $0.withMemoryRebound(to: CChar.self, capacity: 1) { ptr in String.init(validatingUTF8: ptr) } } return modelCode ?? "unknown" } var modelHasDynamicIsland: Bool { switch self.deviceModelCode { case "iPhone15,2", // iPhone 14 Pro "iPhone15,3", // iPhone 14 Pro Max "iPhone15,4", // iPhone 15 "iPhone15,5", // iPhone 15 Plus "iPhone16,1", // iPhone 15 Pro "iPhone16,2": // iPhone 15 Pro Max return true default: return false } } }