[iOS] UIDevice

728x90

 

UIDevice에 대해 다뤄보도록 하겠습니다.

UIDevice

UIDevice 클래스에 접근하면 해당 디바이스의 정보를 알 수 있습니다.
디바이스의 정보를 가지고 어떤방향으로 개발을 해야될지 알아 보겠습니다.

  • 디바이스 이름 / 모델 / 화면 방향 등
  • OS 이름 / 버전
  • 인터페이스 형식 (phone, pad, tv 등)
  • 배터리 정보
  • 근접 센서 정보
  • 멀티태스킹 지원 여부

 

접근 방법

let device = UIDevice.current

 

system버전 보기

예를 들어 현재 iPhone의 버전이 11.4.2라고 했을때 11은 Major, 4는 Minor(public), 2는 Minor(non-public)와 같이 Major, Minor (public), Minor (non-public)가 존재합니다.

또, 앱 개발을 할때 버전별로 API가 다를수 있기때문에 분기처리를 위해 systemVersion을 알아야합니다.

if #available(iOS 9.0, *) {
    print("9.0이상")
} else {
    print("9.0미만")
}

 

systemVersion이라는 IBAction을 만들어서 확인해보겠습니다.

@IBAction private func systemVersion() {

    print("\n---------- [ System Version ] ----------\n")
    print("System Name :", device.systemName)
    print((device.systemVersion as NSString).floatValue) // ex) 11.4라고 했을때 11은 메이저 버전, 4는 마이너 버전
    
    let systemVersion = device.systemVersion
    print(systemVersion)
    
    
    //좀 더 세부적으로 기능을 나누고 싶을때 메이저, 마이너(public), 마이너(non-public)으로 나눠서 개발 할 수 있다.
    let splitVersion = systemVersion.split(separator: ".").compactMap { Int($0) }
    print(splitVersion)
    
    if splitVersion.count > 2 {
      label.text = "\(splitVersion[0]).\(splitVersion[1]).\(splitVersion[2])"
    } else {
      label.text = "\(splitVersion[0]).\(splitVersion[1])"
    }
}

출력화면

---------- [ System Version ] ----------
System Name : iOS
11.4
11.4
[11, 4]

 

아키텍처

시뮬레이터와 Device의 정보를 얻을 수 있습니다.

@IBAction private func architecture() {
  print("\n---------- [ Architecture ] ----------\n")
  
  //#if (arch(i386) || arch(x86_64)) && os(iOS) // 시뮬레이터 구분 방법 1
  #if targetEnvironment(simulator) // 시뮬레이터 구분 방법 2
    print("Simulator")
    label.text = "Simulator"
  #else // Device
    print("Device")
    label.text = "Device"
  #endif
  
  // 1 1 0 1 1 1 1 0 0 0   Simulator
  // 1 1 0 0 0 1 1 0 0 1   Device: iPhoneX
  print("TARGET_OS_MAC : ", TARGET_OS_MAC)
  print("TARGET_OS_IOS : ", TARGET_OS_IOS)
  print("TARGET_CPU_X86 : ", TARGET_CPU_X86)
  print("TARGET_CPU_X86_64 : ", TARGET_CPU_X86_64)
  print("TARGET_OS_SIMULATOR : ", TARGET_OS_SIMULATOR)
  print("TARGET_RT_64_BIT : ", TARGET_RT_64_BIT)
  print("TARGET_RT_LITTLE_ENDIAN :", TARGET_RT_LITTLE_ENDIAN)
  print("TARGET_RT_BIG_ENDIAN :", TARGET_RT_BIG_ENDIAN)
  print("TARGET_CPU_ARM : ", TARGET_CPU_ARM)
  print("TARGET_CPU_ARM64 : ", TARGET_CPU_ARM64)
}

출력화면

1 1 0 1 1 1 1 0 0 0 Simulator
1 1 0 0 0 1 1 0 0 1 Device: iPhoneX
TARGET_OS_MAC : 1
TARGET_OS_IOS : 1
TARGET_CPU_X86 : 0
TARGET_CPU_X86_64 : 0
TARGET_OS_SIMULATOR : 0
TARGET_RT_64_BIT : 1
TARGET_RT_LITTLE_ENDIAN : 1
TARGET_RT_BIG_ENDIAN : 0
TARGET_CPU_ARM : 0
TARGET_CPU_ARM64 : 1

 

디바이스 모델

@IBAction private func deviceModel() {
    print("\n---------- [ Device Model ] ----------\n")
    print("name :", device.name)
    print("model :", device.model)
    print("localizedModel :", device.localizedModel)
    print("userInterfaceIdiom :", device.userInterfaceIdiom)
    print("orientation :", device.orientation)
    /***************************************
     <UIDeviceOrientation>
     
     case unknown
     case portrait // Device oriented vertically, home button on the bottom
     case portraitUpsideDown // Device oriented vertically, home button on the top
     case landscapeLeft // Device oriented horizontally, home button on the right
     case landscapeRight // Device oriented horizontally, home button on the left
     case faceUp // 기기가 위로 놓여져 있을때
     case faceDown // 기기가 뒤짚어 있을때
     ****************************************/
    print("isMultitaskingSupported :", device.isMultitaskingSupported) // 최신 기기에서는 항상 true
}

출력화면

---------- [ Device Model ] ----------
name : 김승진의 iPhone
model : iPhone
localizedModel : iPhone
userInterfaceIdiom : phone
orientation : UIDeviceOrientation

 

위의 출력 결과를 보면 iPhone에 대한 인지는하고 있지만 iPhone의 종류가 무엇인지 세세하게 나와있지 않습니다.
보통 정확한 정보를 위해 Custom하여 Device에 대한 정보를 표현해냅니다.

extension UIDevice {

    // 상세한 모델을 표현하기 위한 로직
    var modelIdentifier: String {
        var sysinfo = utsname()
        uname(&sysinfo)
        let data = Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN))
        let identifier = String(bytes: data, encoding: .ascii)!
        return identifier.trimmingCharacters(in: .controlCharacters)
    }

    // 모델 프로퍼티
    var modelName: String {
        return deviceModelMappingList[modelIdentifier] ?? "Unknown"
    }

    // 디바이스 모델에 대한 상세정보를 저장해 놓습니다.
    // 세세한 정보에 대해 분기처리를 할 경우 이런식으로 모델에 대한정보를 뽑아 올 수 있습니다.
    private var deviceModelMappingList: [String: String] {
        return [
            "iPhone1,1" : "iPhone",            // (Original)
            "iPhone1,2" : "iPhone",            // (3G)
            "iPhone2,1" : "iPhone",            // (3GS)
            "iPhone3,1" : "iPhone 4",          // (GSM)
            "iPhone3,3" : "iPhone 4",          // (CDMA)
            "iPhone4,1" : "iPhone 4S",         //
            "iPhone5,1" : "iPhone 5",          // (A1428)
            "iPhone5,2" : "iPhone 5",          // (A1429)
            "iPhone5,3" : "iPhone 5c",         // (A1456/A1532)
            "iPhone5,4" : "iPhone 5c",         // (A1507/A1516/A1529)
            "iPhone6,1" : "iPhone 5s",         // (A1433, A1453)
            "iPhone6,2" : "iPhone 5s",         // (A1457, A1518, A1530)
            "iPhone7,1" : "iPhone 6 Plus",     //
            "iPhone7,2" : "iPhone 6",          //
            "iPhone8,1" : "iPhone 6S",         //
            "iPhone8,2" : "iPhone 6S Plus",    //
            "iPhone8,4" : "iPhone SE",         //
            "iPhone9,1" : "iPhone 7",          // (A1660/A1779/A1780)
            "iPhone9,3" : "iPhone 7",          // (A1778)
            "iPhone9,2" : "iPhone 7 Plus",     // (A1661/A1785/A1786)
            "iPhone9,4" : "iPhone 7 Plus",     // (A1784)
            "iPhone10,1": "iPhone 8",          // (A1863/A1906) CDMA
            "iPhone10,4": "iPhone 8",          // (A1905) GSM
            "iPhone10,2": "iPhone 8 Plus",     // (A1864/A1898) CDMA
            "iPhone10,5": "iPhone 8 Plus",     // (A1897) GSM
            "iPhone10,3": "iPhone X",          // (A1865/A1902) CDMA
            "iPhone10,6": "iPhone X",          // (A1901) GSM

            "iPad1,1"   : "iPad",              // (Original)
            "iPad2,1"   : "iPad 2",            // (Wi-Fi)
            "iPad2,2"   : "iPad 2",            // (GSM)
            "iPad2,3"   : "iPad 2",            // (CDMA)
            "iPad2,4"   : "iPad 2",            // (Wi-Fi, revised)
            "iPad2,5"   : "iPad Mini",         // (Wi-Fi)
            "iPad2,6"   : "iPad Mini",         // (A1454)
            "iPad2,7"   : "iPad Mini",         // (A1455)
            "iPad3,1"   : "iPad 3",            // (3rd Generation, Wi-Fi)
            "iPad3,2"   : "iPad 3",            // (3rd Generation, Wi-Fi+LTE Verizon)
            "iPad3,3"   : "iPad 3",            // (3rd Generation, Wi-Fi+LTE AT&T)
            "iPad3,4"   : "iPad 4",            // (4th Generation, Wi-Fi)
            "iPad3,5"   : "iPad 4",            // (4th Generation, A1459)
            "iPad3,6"   : "iPad 4",            // (4th Generation, A1460)
            "iPad4,1"   : "iPad Air",          // (Wifi)
            "iPad4,2"   : "iPad Air",          // (Wi-Fi+LTE)
            "iPad4,3"   : "iPad Air",          // (Rev)
            "iPad4,4"   : "iPad Mini 2",       // (2nd Generation, Wifi)
            "iPad4,5"   : "iPad Mini 2",       // (2nd Generation, Wi-Fi+LTE)
            "iPad4,6"   : "iPad Mini 2",       // (2nd Generation, Rev)
            "iPad4,7"   : "iPad Mini 3",       // (3rd Generation, Wifi, A1599)
            "iPad4,8"   : "iPad Mini 3",       // (3rd Generation, A1600)
            "iPad4,9"   : "iPad Mini 3",       // (3rd Generation, A1601)
            "iPad5,1"   : "iPad Mini 4",       // (Wi-Fi)
            "iPad5,2"   : "iPad Mini 4",       // (Wi-Fi+LTE)
            "iPad5,3"   : "iPad Air 2",        // (Wi-Fi)
            "iPad5,4"   : "iPad Air 2",        // (Wi-Fi+LTE)
            "iPad6,3"   : "iPad Pro (9.7 inch)",  // (Wi-Fi)
            "iPad6,4"   : "iPad Pro (9.7 inch)",  // (Wi-Fi+LTE)
            "iPad6,7"   : "iPad Pro (12.9 inch)", // (Wi-Fi)
            "iPad6,8"   : "iPad Pro (12.9 inch)", // (Wi-Fi+LTE)
            "iPad6,11"  : "iPad (9.7 inch)",      // 5th Gen (Wi-Fi)
            "iPad6,12"  : "iPad (9.7 inch)",      // 5th Gen (Wi-Fi+LTE)
            "iPad7,3"   : "iPad Pro (10.5 inch)", // (A1701)
            "iPad7,4"   : "iPad Pro (10.5 inch)", // (A1709)

            "iPod1,1"   : "iPod Touch",        // (Original)
            "iPod2,1"   : "iPod Touch 2",      // (2nd Generation)
            "iPod3,1"   : "iPod Touch 3",      // (3rd Generation)
            "iPod4,1"   : "iPod Touch 4",      // (4th Generation)
            "iPod5,1"   : "iPod Touch 5",      // (5th Generation)
            "iPod7,1"   : "iPod Touch 6",      // (6th Generation)

            "i386"      : "Simulator",         // 32 bit
            "x86_64"    : "Simulator",         // 64 bit
        ]
  }
}
//extension
print("modelIdentifier :", device.modelIdentifier)
print("modelName :", device.modelName)

출력화면

---------- [ Device Model ] ----------
name : 김승진의 iPhone
model : iPhone
localizedModel : iPhone
userInterfaceIdiom : phone
orientation : UIDeviceOrientation
modelIdentifier : iPhone10,6
modelName : iPhone X

 

배터리 정보

배터리 상태에는 unknown, unplugged, charging, full 상태가 존재합니다.

IDeviceBatteryState : Int {
    case unknown
    case unplugged // on battery, discharging
    case charging  // plugged in, less than 100%
    case full      // plugged in, at 100%
}

 

배터리의 정보를 확인 하는 버튼을 만들었습니다.

@IBAction private func battery() {
    print("\n-------------------- [ Battery Info ] --------------------\n")
    label.text = "\(device.batteryState) : \(device.batteryLevel)"
    print("batteryState :", device.batteryState)
    print("batteryLevel :", device.batteryLevel)
    print("isBatteryMonitoringEnabled :", device.isBatteryMonitoringEnabled)
}

위에 코드대로 바로 실행해보면 아래와 같이 출력화면을 볼 수 있습니다.

-------------------- [ Battery Info ] --------------------
batteryState : unknown
batteryLevel : -1.0
isBatteryMonitoringEnabled : false

 

기본적으로 isBatteryMonitoringEnabled는 false로 설정되어있기 때문입니다.

 

그래서 배터리 정보를 모니터링 할 수있는 활성화버튼을 하나 만들었습니다.

@IBAction private func batteryMonitoring(_ sender: UIButton) {
    print("\n---------- [ batteryMonitoring ] ----------\n")
    sender.isSelected = !sender.isSelected // 버튼을 눌렀을시 토글기능

    //isBtteryMonitoringEnabled의 활성화 상태를 토글
    //버튼 선택시 true로 바꾸기위한 작업.
    //true로 바꿔야 정보를 가져 올 수 있다.
    device.isBtteryMonitoringEnabled = !device.isBatteryMonitoringEnabled

    //Notification의 UIDeviceBatteryStateDidChange의 상태로 정보를 가져온다.
    if device.isBatteryMonitoringEnabled {
      NotificationCenter.default.addObserver(
        forName: .UIDeviceBatteryStateDidChange, object: nil, queue: .main
      ) {
        if let device = $0.object as? UIDevice {
          print("batteryState :", device.batteryState)
          print("batteryLevel :", device.batteryLevel)
        }
      }
    } else {
      NotificationCenter.default.removeObserver(self, name: .UIDeviceBatteryStateDidChange, object: nil)
    }
}

isBtteryMonitoringEnabled를 true의 상태로 바꿔주고 NotificationCenter로 UIDeviceBatteryStateDidChange을 감지하여 배터리의 정보를 가져오게끔 완성했습니다.
다시한번 battery() 버튼을 선택해보겠습니다.

 

출력화면

-------------------- [ Battery Info ] --------------------
batteryState : full
batteryLevel : 1.0
isBatteryMonitoringEnabled : true

 

근접 센서 정보

근접센서 proximity를 다뤄보겠습니다.

근접센서를 다루기 위해서는 먼저 활성화를 시켜줘야합니다.

device.isProximityMonitoringEnabled = true

활성화를 시켜줬다면 device기기에서 통화음이 들리는 곳에 위치한 근접센서에 손을 일정시간(0.5초?..)유지를 하고 있으면 화면이 꺼지는걸 볼 수 있을겁니다!
즉, 근접센서에 손이 가까이가면 true상태, 근접센서에 접근하는것이 없다면 false상태로 계속해서 유동적으로 변하게됩니다.

 

이렇게 상태가 변하는걸 보기위해서는 배터리의 상태정보를 확인하는거와 마찬가지로 Notification으로 상태확인이 가능합니다.

NotificationCenter.default.addObserver(
        forName: .UIDeviceProximityStateDidChange, object: nil, queue: .main
) { [weak self] _ in
        print(UIDevice.current.proximityState)
        self?.label.text = "\(UIDevice.current.proximityState)"
}

label의 값이 true, false로 상태가 계속적으로 변하는것을 볼 수 있습니다.

@IBAction private func proximityMonitoring(_ sender: UIButton) {
    
    // True가 어느정도 유지되는순간 화면이 꺼진다.
    
    print("\n-------------------- [ Proximity Sensor ] --------------------\n")
    sender.isSelected = !sender.isSelected
    
    device.isProximityMonitoringEnabled = !device.isProximityMonitoringEnabled
    print("ProximityMonitoring :", device.isProximityMonitoringEnabled)
    
    if device.isProximityMonitoringEnabled {
        //NotificationCenter에 UIDeviceProximityStateDidChange의 상태로 근접센서를 다룰 수 있습니다.
      NotificationCenter.default.addObserver(
        forName: .UIDeviceProximityStateDidChange, object: nil, queue: .main
      ) { [weak self] _ in
        print(UIDevice.current.proximityState)
        self?.label.text = "\(UIDevice.current.proximityState)"
      }
    } else {
     NotificationCenter.default.removeObserver(self, name: .UIDeviceProximityStateDidChange, object: nil)
    }
}

출력화면

-------------------- [ Proximity Sensor ] --------------------
ProximityMonitoring : true
true
false
true
false

 

화면 방향

UIDeviceOrientation(화면방향)의 상태는 unknown, portrait,portraitUpsideDown, landscapeLeft, landscapeRight, faceUp, faceDown가 있습니다.

직접 디바이스의 기기로 확인하기전에 제어화면에서 기기화면 고정옵션을 켜두었는지 확인해야합니다.

public enum UIDeviceOrientation : Int {
    case unknown
    case portrait        // Device oriented vertically, home button on the bottom
    case portraitUpsideDown // Device oriented vertically, home button on the top
    case landscapeLeft   // Device oriented horizontally, home button on the right
    case landscapeRight  // Device oriented horizontally, home button on the left
    case faceUp          // Device oriented flat, face up
    case faceDown        // Device oriented flat, face down
}

기본적으로 기기가 회전이 가능하게끔 true로 설정되어있습니다.
회전을 지원하기 위해서는 beginGeneratingDeviceOrientationNotifications()를 활성화 시켜줘야합니다.

print(device.isGeneratingDeviceOrientationNotifications) // true
device.beginGeneratingDeviceOrientationNotifications() // Orientation을 지원하기위해 사용

//반대로는 endGeneratingDeviceOrientationNotifications()가 있습니다.

실행해보면 화면이 돌아가는것을 볼 수가있습니다. 또 반대로 endGeneratingDeviceOrientationNotifications()를 설정하면 화면이 돌아가지 않는것을 확인 할 수 있습니다.

디바이스 회전 방향도 Notification으로 상태를 체크할 수 있습니다. (UIDeviceOrientationDidChange)

NotificationCenter.default.addObserver(
      self, selector: #selector(orientationDidChange(_:)), name: .UIDeviceOrientationDidChange, object: nil
)

@objc func orientationDidChange(_ noti: Notification) {
    print("\n-------------------- [ Orientation didChange ] --------------------\n")
    if let device = noti.object as? UIDevice {
      print("Device Orientation :", device.orientation)
    }
    print("StatusBar Orientation :", UIApplication.shared.statusBarOrientation)
}

출력화면

-------------------- [ Orientation didChange ] --------------------
true
Device Orientation : UIDeviceOrientation
StatusBar Orientation : UIInterfaceOrientation

 

StatusBar Orientation은 조금있다가 확인해보겠습니다.

회전을 감지해서 상태가 UIDeviceOrientation이라는 것을 감지했지만 오른쪽, 왼쪽인지 정확한 상태가 나오지 않습니다.

어떤상태인지 디버깅창에 자세히 보이게 하기위해 CustomStringConvertible을 통해 상세히 나타내보도록 하겠습니다.

extension UIDeviceOrientation: CustomStringConvertible {
  public var description: String {
    switch self {
    case .unknown: return "unknown"
    case .portrait: return "portrait"
    case .portraitUpsideDown: return "portraitUpsideDown"
    case .landscapeLeft: return "landscapeLeft"
    case .landscapeRight: return "landscapeRight"
    case .faceUp: return "faceUp"
    case .faceDown: return "faceDown"
    }
  }
}

extension UIInterfaceOrientation: CustomStringConvertible {
  public var description: String {
    switch self {
    case .unknown: return "unknown"
    case .portrait: return "portrait"
    case .portraitUpsideDown: return "portraitUpsideDown"
    case .landscapeLeft: return "landscapeLeft"
    case .landscapeRight: return "landscapeRight"
    }
  }
}

위의 CustomStringConvertible을 통해 description을 활용하면 원하는 값으로 정보를 바꿀수 있습니다.
다시한번 출력값을 확인해보겠습니다.

출력화면

-------------------- [ Orientation didChange ] --------------------
true
Device Orientation : landscapeRight
StatusBar Orientation : landscapeLeft

 

기기를 오른쪽으로 돌렸을때(home button쪽이 왼쪽인 상태를 말합니다.) 정확히 Device Orientation에 landscapeRight라고 잘 나옵니다.
하지만 StatusBar Orientation이라는 상태바의 위치를 나타내는 놈은 반대를 가리키고 있습니다.

 

UIInterfaceOrientation 과 UIDeviceOrientation 의 차이 주의

UIApplication.shared.statusBarOrientation - statusBar 의 위치 기준입니다.

public enum UIInterfaceOrientation : Int {
     case unknown
     case portrait
     case portraitUpsideDown
     case landscapeLeft
     case landscapeRight
}
  • UIDeviceOrientation : 디바이스 기준

  • UIInterfaceOrientation : 컨텐츠 기준

  • UIDeviceOrientation.landscapeRight = UIInterfaceOrientation.landscapeLeft

  • UIDeviceOrientation.landscapeLeft = UIInterfaceOrientation.landscapeRight

 

디바이스를 잘 살펴보면 디바이스를 왼쪽으로(landscapeLeft)로 돌리면 화면은 오른쪽으로 회전하는걸 볼 수 있습니다. 즉 화면(컨텐츠)는 오른쪽으로 회전하니까 디바이스는 왼쪽(landscapeLeft) 회전할때 화면은 오른쪽(landscapeRight)인겁니다.
이로써 두 가지의 Orientation을 사용할때는 주의해서 사용해야됩니다.

'iOS' 카테고리의 다른 글

[iOS] Photos Framework  (0) 2019.07.25
[iOS] iOS Realm이란?  (0) 2019.07.24
[iOS] GCD(Grand Central Dispatch)  (0) 2019.07.24
ARC(Automatic Reference Counting)  (0) 2019.07.24
[iOS] 단축키(Shortcuts)  (0) 2019.07.24