0

Normally, I'd like to test my iOS-App for different devices. Since I don't have the money to buy all devices, I use the Simulator to do so :)

Often times, code looks slightly different for the different form-factors of Apple products. Therefore I came up with the following switch case (see below...) to distinguish programmatically between devices.

The switch-case works very well for actual HW-devices - but it is still somehow a bad solution for using the Simulator.

The Simulator is its own Device ! And I need to uncomment/comment in each and every switch-case if I change to a Simulator-target with different form-factor !

Is there a better solution that takes better care of the Simulator and its form-factors programmatically ?

switch UIDevice.current.modelName {

    // Here there is still the need to uncomment/comment the Devices.Simulator
    // for any new form-factor that is being simulated....

    // as an example: If simulating an iPhone-8S then the Devices.Simulator needs to be uncommented in the according switch-case...

    case Devices.IPhone5, Devices.IPhone5S, Devices.IPhone5C: //, Devices.Simulator:
        print("do whatever...")
    case Devices.IPhone6, Devices.IPhone6S, Devices.IPhone7, Devices.IPhone8: //, Devices.Simulator:
        print("do whatever...")
    case Devices.IPhone6Plus, Devices.IPhone6SPlus, Devices.IPhone7Plus, Devices.IPhone8Plus, Devices.Simulator:
        print("do whatever...")
    case Devices.IPhoneX: //, Devices.Simulator:
        print("do whatever...")
    default:
        print("do whatever...")
}

With, of course, the following somewhere in your code-base:

public enum Devices: String {
    case IPodTouch5
    case IPodTouch6
    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 IPad2
    case IPad3
    case IPad4
    case IPad5
    case IPadAir
    case IPadAir2
    case IPadMini
    case IPadMini2
    case IPadMini3
    case IPadMini4
    case IPadPro_9_7
    case IPadPro_12_9
    case IPadPro_12_9_2ndGen
    case IPadPro_10_5
    case AppleTV_5_3
    case AppleTV_6_2
    case HomePod
    case Simulator
    case Other
}

And ...

public extension UIDevice {

    public var modelName: Devices {
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.reduce("") { identifier, element in
            guard let value = element.value as? Int8 , value != 0 else { return identifier }
            return identifier + String(UnicodeScalar(UInt8(value)))
        }

        switch identifier {
        case "iPod5,1":                                 return Devices.IPodTouch5
        case "iPod7,1":                                 return Devices.IPodTouch6
        case "iPhone3,1", "iPhone3,2", "iPhone3,3":     return Devices.IPhone4
        case "iPhone4,1":                               return Devices.IPhone4S
        case "iPhone5,1", "iPhone5,2":                  return Devices.IPhone5
        case "iPhone5,3", "iPhone5,4":                  return Devices.IPhone5C
        case "iPhone6,1", "iPhone6,2":                  return Devices.IPhone5S
        case "iPhone7,2":                               return Devices.IPhone6
        case "iPhone7,1":                               return Devices.IPhone6Plus
        case "iPhone8,1":                               return Devices.IPhone6S
        case "iPhone8,2":                               return Devices.IPhone6SPlus
        case "iPhone9,1", "iPhone9,3":                  return Devices.IPhone7
        case "iPhone9,2", "iPhone9,4":                  return Devices.IPhone7Plus
        case "iPhone8,4":                               return Devices.IPhoneSE
        case "iPhone10,1", "iPhone10,4":                return Devices.IPhone8
        case "iPhone10,2", "iPhone10,5":                return Devices.IPhone8Plus
        case "iPhone10,3", "iPhone10,6":                return Devices.IPhoneX
        case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return Devices.IPad2
        case "iPad3,1", "iPad3,2", "iPad3,3":           return Devices.IPad3
        case "iPad3,4", "iPad3,5", "iPad3,6":           return Devices.IPad4
        case "iPad4,1", "iPad4,2", "iPad4,3":           return Devices.IPadAir
        case "iPad5,3", "iPad5,4":                      return Devices.IPadAir2
        case "iPad6,11", "iPad6,12":                    return Devices.IPad5
        case "iPad2,5", "iPad2,6", "iPad2,7":           return Devices.IPadMini
        case "iPad4,4", "iPad4,5", "iPad4,6":           return Devices.IPadMini2
        case "iPad4,7", "iPad4,8", "iPad4,9":           return Devices.IPadMini3
        case "iPad5,1", "iPad5,2":                      return Devices.IPadMini4
        case "iPad6,3", "iPad6,4":                      return Devices.IPadPro_9_7
        case "iPad6,7", "iPad6,8":                      return Devices.IPadPro_12_9
        case "iPad7,1", "iPad7,2":                      return Devices.IPadPro_12_9_2ndGen
        case "iPad7,3", "iPad7,4":                      return Devices.IPadPro_10_5
        case "AppleTV5,3":                              return Devices.AppleTV_5_3
        case "AppleTV6,2":                              return Devices.AppleTV_6_2
        case "AudioAccessory1,1":                       return Devices.HomePod
        case "i386", "x86_64":                          return Devices.Simulator
        default:                                        return Devices.Other
        }
    }
}
iKK
  • 6,394
  • 10
  • 58
  • 131
  • 1
    It depends what you're trying to achieve. Apple define size classes in order to manage layout issues: https://www.bignerdranch.com/blog/designing-for-size-classes-in-ios/ – Leon May 01 '18 at 11:10
  • Note that you can retrieve the (simulated) iPhone model when running in the Simulator: https://stackoverflow.com/a/30075200/1187415. – Martin R May 01 '18 at 11:13
  • Most often, I want to distinguish iPhoneSE-sized devices from iPhone6/7/8-sized devices. If you show me a way on how to use size-classes for this problem, then I am more than happy to no longer do it manually myself in code !! (...but to this day, I have not seen a solution to distinguish those two form-factors [i.s. iPhoneSE vs. iPhone6/7/8]) !! – iKK May 01 '18 at 11:15
  • Thank you, Martin R, I was not aware that you can retrieve the simulated model as well !! This solves some of it !! (still remaining the problem of iPhoneSE vs. iPhone6/7/8 (simulator or non-simulator).... – iKK May 01 '18 at 11:17
  • 1
    Why do you want to distinguish between them though? You still haven't mentioned what you're trying to achieve. – Leon May 01 '18 at 11:21
  • Often times, iPhoneSE vs iPhone6/7/8 you cannot use the same UI-constraints (UI's using the SE-constraints on an 6/7/8 looks to much left / and vice-vers: 6/7/8-constraints on a SE make lots of UI elements being compressed. All of those cases I can treat programmatically changing the element-constraints according to form-factor. So it is all about nice looking UI's :) – iKK May 01 '18 at 11:31
  • Right, and your approach is definitely not the way to achieve this. AutoLayout can handle this perfectly fine, using size classes if you want granular control, but this isn't always required. – Leon May 01 '18 at 11:34
  • Can you please give me an example on how AutoLayout does the distinction between iPhoneSE and iPhone6/7/8 formfactors ? – iKK May 01 '18 at 11:50
  • 1
    It doesn't, they're both compact in portrait, but you should be designing a layout that can adapt between the two, that's the point of AutoLayout. Sometimes you have to make compromises (which should be related to the number of users on each) which is entirely normal. Your approach is poor, it's not future proof for a start but it's also just unnecessary and over-engineered. – Leon May 01 '18 at 12:09
  • Thank you, Leon, for your comment. It confirms that size-classes cannot distinguish iPhoneSE from iPhone6/7/8. And please, let the necessity for a distinction between the two form-factors be of my worries ;) – iKK May 01 '18 at 12:11

2 Answers2

0

I found a better solution see "With the input from Martin R,...."

iKK
  • 6,394
  • 10
  • 58
  • 131
0

With the input from Martin R, I have now a nice solution without any need of the user to ever type something manual into the code when making distinctions between iOS-devices' form-factors (whether HW or Simulator) ! [of course, new future Apple devices excluded...]

And best of all: It is also a solution to distinguish iPhoneSE from iPhone6/7/8 form-factors !

struct AppConstants {

    // feature flags
    struct FEATUREFLAG {

        static let DEVICE_MODEL_NAME = { () -> Devices in
            switch UIDevice.current.modelName {
            case Devices.Simulator:
                // set featureFlag for different form-factors to be simulated...
                return UIDevice.current.simulatorModelName
            default:
                // set featureFlag for different form-factors on actual device...
                return UIDevice.current.modelName
            }
        }()
    }
}

And the change in the Device extension :

public extension UIDevice {

    public var modelName: Devices {
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.reduce("") { identifier, element in
            guard let value = element.value as? Int8 , value != 0 else { return identifier }
            return identifier + String(UnicodeScalar(UInt8(value)))
        }
        return self.getDeviceFromIdentifier(identifier: identifier)
    }

    public var simulatorModelName: Devices {

        if let simulatorModelIdentifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { return getDeviceFromIdentifier(identifier: simulatorModelIdentifier) }
        var sysinfo = utsname()
        uname(&sysinfo) // ignore return value
        return getDeviceFromIdentifier(identifier: String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters))
    }

    private func getDeviceFromIdentifier(identifier: String) -> Devices {
        switch identifier {
        case "iPod5,1":                                 return Devices.IPodTouch5
        case "iPod7,1":                                 return Devices.IPodTouch6
        case "iPhone3,1", "iPhone3,2", "iPhone3,3":     return Devices.IPhone4
        case "iPhone4,1":                               return Devices.IPhone4S
        case "iPhone5,1", "iPhone5,2":                  return Devices.IPhone5
        case "iPhone5,3", "iPhone5,4":                  return Devices.IPhone5C
        case "iPhone6,1", "iPhone6,2":                  return Devices.IPhone5S
        case "iPhone7,2":                               return Devices.IPhone6
        case "iPhone7,1":                               return Devices.IPhone6Plus
        case "iPhone8,1":                               return Devices.IPhone6S
        case "iPhone8,2":                               return Devices.IPhone6SPlus
        case "iPhone9,1", "iPhone9,3":                  return Devices.IPhone7
        case "iPhone9,2", "iPhone9,4":                  return Devices.IPhone7Plus
        case "iPhone8,4":                               return Devices.IPhoneSE
        case "iPhone10,1", "iPhone10,4":                return Devices.IPhone8
        case "iPhone10,2", "iPhone10,5":                return Devices.IPhone8Plus
        case "iPhone10,3", "iPhone10,6":                return Devices.IPhoneX
        case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return Devices.IPad2
        case "iPad3,1", "iPad3,2", "iPad3,3":           return Devices.IPad3
        case "iPad3,4", "iPad3,5", "iPad3,6":           return Devices.IPad4
        case "iPad4,1", "iPad4,2", "iPad4,3":           return Devices.IPadAir
        case "iPad5,3", "iPad5,4":                      return Devices.IPadAir2
        case "iPad6,11", "iPad6,12":                    return Devices.IPad5
        case "iPad2,5", "iPad2,6", "iPad2,7":           return Devices.IPadMini
        case "iPad4,4", "iPad4,5", "iPad4,6":           return Devices.IPadMini2
        case "iPad4,7", "iPad4,8", "iPad4,9":           return Devices.IPadMini3
        case "iPad5,1", "iPad5,2":                      return Devices.IPadMini4
        case "iPad6,3", "iPad6,4":                      return Devices.IPadPro_9_7
        case "iPad6,7", "iPad6,8":                      return Devices.IPadPro_12_9
        case "iPad7,1", "iPad7,2":                      return Devices.IPadPro_12_9_2ndGen
        case "iPad7,3", "iPad7,4":                      return Devices.IPadPro_10_5
        case "AppleTV5,3":                              return Devices.AppleTV_5_3
        case "AppleTV6,2":                              return Devices.AppleTV_6_2
        case "AudioAccessory1,1":                       return Devices.HomePod
        case "i386", "x86_64":                          return Devices.Simulator
        default:                                        return Devices.Other
        }
    }
}

And :

public enum Devices: String {
    case IPodTouch5
    case IPodTouch6
    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 IPad2
    case IPad3
    case IPad4
    case IPad5
    case IPadAir
    case IPadAir2
    case IPadMini
    case IPadMini2
    case IPadMini3
    case IPadMini4
    case IPadPro_9_7
    case IPadPro_12_9
    case IPadPro_12_9_2ndGen
    case IPadPro_10_5
    case AppleTV_5_3
    case AppleTV_6_2
    case HomePod
    case Simulator
    case Other
}
iKK
  • 6,394
  • 10
  • 58
  • 131