Differentiating between Apple mobile devices programmatically

It is fairly well known that Apple gives very little information away in the User Agent. All it really surrenders is the device family (e.g. iPhone, iPad, iPod) and the iOS version.

There is an ongoing argument about whether it is appropriate to use device detection to modify the appearance of your website – many argue that feature detection is far superior, and any site intended for a mobile/tablet audience in particular should be responsive. To be perfectly honest, it’s not an argument I really want to get involved in. There are reasons why you may still wish to identify which physical device a visitor is using, rather than just which iOS version and device family.

For example, not all iPhones are equal – if nothing else, the performance of the iPhone 3G is very poor when compared to the iPhone 5. If you are developing a site which is CPU intensive then optimising the site for the iPhone 3G may be a costly and time consuming exercise. If only a small percentage of your customer base uses the iPhone 3G, then the business case for optimising the site may not stack up.

That is just one example of why it may be valuable to know which physical devices your customers use – there are plenty of others. Regardless of why you want to detect it, the method detailed here may help you to do it.

I’ve hacked together the code below (it should work) to attempt to identify the specific device version of an iPod Touch, iPhone or iPad. The following buckets are available:

  • iPod Touch
    • 1 or 2 or 3
    • 4
    • 5
  • iPhone
    • 3G or 3GS
    • 4
    • 4 or 4S
    • 5
  • iPad
    • 1
    • 2
    • 2 or Mini
    • 3 or 4 (Retina)
Where the buckets overlap (i.e. “4″ or “4 or 4S” and “2″ or “2 or Mini”), this is because it attempts to use the iOS version to deduce it where possible – i.e. the if the iOS is 4.x then it must be an iPhone 4 not an iPhone 4S.
  1. function detectDevice(cb) {
  2.     var getDeviceFamily = function() {
  3.         var ua = window.navigator.userAgent;
  4.         switch (true) {
  5.             case (-1 !== ua.indexOf('iPod')) :
  6.                 return 'iPod Touch';
  7.             case (-1 !== ua.indexOf('iPhone')) :
  8.                 return 'iPhone';
  9.             case (-1 !== ua.indexOf('iPad')) :
  10.                 return 'iPad';
  11.         };
  12.         return null;
  13.     };
  14.  
  15.     var getOSVersion = function() {
  16.         var version = window.navigator.userAgent.match(/OS ([0-9]+)_([0-9]+)/);
  17.         if (null === version) {
  18.             return null;
  19.         }
  20.  
  21.         return parseInt(version[1]) + parseInt(version[2]) / 10;
  22.     };
  23.  
  24.     var family = getDeviceFamily();
  25.     var version = getOSVersion();
  26.  
  27.     if (null === family || null === version) {
  28.         cb('Unknown');
  29.         return;
  30.     }
  31.  
  32.     var device = 'Unknown';
  33.  
  34.     var decide = function(gyro) {
  35.         var retina = 2 === window.devicePixelRatio;
  36.  
  37.         device = family;
  38.         switch (family) {
  39.             case 'iPad' :
  40.                 if (retina) {
  41.                     device += ' 3 or 4 (Retina)';
  42.                 } else {
  43.                     if (gyro) {
  44.                         device += (version < 6 ? ' 2' : ' 2 or Mini');
  45.                     } else {
  46.                         device += ' 1';
  47.                     }
  48.                 }
  49.                 break;
  50.  
  51.             case 'iPhone' :
  52.                 switch (window.screen.width + ' x ' + window.screen.height) {
  53.                     case '320 x 480':
  54.                         if (retina) {
  55.                             device += (version < 5 ? ' 4' : ' 4 or 4S');
  56.                         } else {
  57.                             device +=  ' 3G or 3GS';
  58.                         }
  59.                         break;
  60.  
  61.                     case '320 x 568':
  62.                         device += ' 5';
  63.                         break;
  64.                 }
  65.                 break;
  66.  
  67.             case 'iPod Touch' :
  68.                 switch (window.screen.width + ' x ' + window.screen.height) {
  69.                     case '320 x 480':
  70.                         device += (retina ? ' 4' : ' 1 or 2 or 3');
  71.                         break;
  72.  
  73.                     case '320 x 568':
  74.                         device += ' 5';
  75.                         break;
  76.                 }
  77.                 break;
  78.         };
  79.  
  80.         cb(device);
  81.     };
  82.  
  83.     if (undefined != window.DeviceMotionEvent) {
  84.         window.ondevicemotion = function(e) {
  85.             window.ondevicemotion = null;
  86.             decide(null !== e.rotationRate);
  87.         }
  88.     } else {
  89.         decide(false);
  90.     }
  91. };
function detectDevice(cb) {
    var getDeviceFamily = function() {
        var ua = window.navigator.userAgent;
        switch (true) {
            case (-1 !== ua.indexOf('iPod')) :
                return 'iPod Touch';
            case (-1 !== ua.indexOf('iPhone')) :
                return 'iPhone';
            case (-1 !== ua.indexOf('iPad')) :
                return 'iPad';
        };
        return null;
    };

    var getOSVersion = function() {
        var version = window.navigator.userAgent.match(/OS ([0-9]+)_([0-9]+)/);
        if (null === version) {
            return null;
        }

        return parseInt(version[1]) + parseInt(version[2]) / 10;
    };

    var family = getDeviceFamily();
    var version = getOSVersion();

    if (null === family || null === version) {
        cb('Unknown');
        return;
    }

    var device = 'Unknown';

    var decide = function(gyro) {
        var retina = 2 === window.devicePixelRatio;

        device = family;
        switch (family) {
            case 'iPad' :
                if (retina) {
                    device += ' 3 or 4 (Retina)';
                } else {
                    if (gyro) {
                        device += (version < 6 ? ' 2' : ' 2 or Mini');
                    } else {
                        device += ' 1';
                    }
                }
                break;

            case 'iPhone' :
                switch (window.screen.width + ' x ' + window.screen.height) {
                    case '320 x 480':
                        if (retina) {
                            device += (version < 5 ? ' 4' : ' 4 or 4S');
                        } else {
                            device +=  ' 3G or 3GS';
                        }
                        break;

                    case '320 x 568':
                        device += ' 5';
                        break;
                }
                break;

            case 'iPod Touch' :
                switch (window.screen.width + ' x ' + window.screen.height) {
                    case '320 x 480':
                        device += (retina ? ' 4' : ' 1 or 2 or 3');
                        break;

                    case '320 x 568':
                        device += ' 5';
                        break;
                }
                break;
        };

        cb(device);
    };

    if (undefined != window.DeviceMotionEvent) {
        window.ondevicemotion = function(e) {
            window.ondevicemotion = null;
            decide(null !== e.rotationRate);
        }
    } else {
        decide(false);
    }
};

The above code makes use of the following properties to detect the device version:

  • User Agent (can work out device family and iOS version from this)
  • Screen Resolution
  • Device Pixel Density (i.e. whether it is a retina display or not)
  • Gyroscope Support (the original iPad didn’t have full gyroscope support)

I hope the approach might be useful to people. If you try it and find bugs, let me know and I’ll update the code.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">