474

I'm wondering if it's possible to detect whether a browser is running on iOS, similar to how you can feature detect with Modernizr (although this is obviously device detection rather than feature detection).

Normally I would favour feature detection instead, but I need to find out whether a device is iOS because of the way they handle videos as per this question YouTube API not working with iPad / iPhone / non-Flash device

Community
  • 1
  • 1
SparrwHawk
  • 11,653
  • 21
  • 57
  • 89

17 Answers17

936

Detecting iOS

With iOS 13 iPad both User agent and platform strings are changed and differentiating between iPad and MacOS seems possible, so all answers below needs to take that into account now.

This might be the shortest alternative that also covers iOS 13:

function iOS() {
  return [
    'iPad Simulator',
    'iPhone Simulator',
    'iPod Simulator',
    'iPad',
    'iPhone',
    'iPod'
  ].includes(navigator.platform)
  // iPad on iOS 13 detection
  || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
}

iOS will be either true or false

Worse option: User agent sniffing

User Agent sniffing is more dangerous and problems appear often.

On iPad iOS 13, the user agent is identical with that of a MacOS 13 computer, but if you ignore iPads this might work still for a while:

var iOS = !window.MSStream && /iPad|iPhone|iPod/.test(navigator.userAgent); // fails on iPad iOS 13

The !window.MSStream is to not incorrectly detect IE11, see here and here.

Note: Both navigator.userAgent and navigator.platform can be faked by the user or a browser extension.

Browser extensions to change userAgent or platform exist because websites use too heavy-handed detection and often disable some features even if the user's browser would otherwise be able to use that feature.

To de-escalate this conflict with users it's recommended to detect specifically for each case the exact features that your website needs. Then when the user gets a browser with the needed feature it will already work without additional code changes.

Detecting iOS version

The most common way of detecting the iOS version is by parsing it from the User Agent string. But there is also feature detection inference*;

We know for a fact that history API was introduced in iOS4 - matchMedia API in iOS5 - webAudio API in iOS6 - WebSpeech API in iOS7 and so on.

Note: The following code is not reliable and will break if any of these HTML5 features is deprecated in a newer iOS version. You have been warned!

function iOSversion() {

  if (iOS) { // <-- Use the one here above
    if (window.indexedDB) { return 'iOS 8 and up'; }
    if (window.SpeechSynthesisUtterance) { return 'iOS 7'; }
    if (window.webkitAudioContext) { return 'iOS 6'; }
    if (window.matchMedia) { return 'iOS 5'; }
    if (window.history && 'pushState' in window.history) { return 'iOS 4'; }
    return 'iOS 3 or earlier';
  }

  return 'Not an iOS device';
}
Paul Rumkin
  • 5,498
  • 2
  • 20
  • 33
Pierre
  • 17,004
  • 4
  • 38
  • 61
  • 2
    Thanks Pierre - this code seems simpler though, I just wonder whether I can just specify 'iOS' rather than having to type out all the separate iDevices.... if((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) { // Do something } – SparrwHawk Jan 28 '12 at 10:22
  • As far as i know there aren't any properties that include the word *iOS*. I however simplified the regex *(but still, device names are needed)*. – Pierre Jan 28 '12 at 14:23
  • 1
    navigator.appVersion is the browser version not the iOS version! http://www.w3schools.com/jsref/prop_nav_appversion.asp – viebel Feb 19 '12 at 16:03
  • 2
    `document.write("Browser Version: " + navigator.appVersion)` It's the browser version and not the iOS version. – viebel Mar 05 '12 at 13:47
  • 10
    What you're doing in the second snippet is feature inference, not feature detection. Feature detection is testing features that you're actually going to use, whereas what you're doing is testing features that you happen to know were introduced in a particular version of the OS and inferring the OS version from them. This is fragile because future versions of iOS could remove these features. – Tim Down Apr 02 '12 at 16:41
  • @Pierre also parsing iOS ins't all safe, the older versions of iOS >4 was refered as iPhoneOS including on the iPad – Vitim.us Aug 01 '12 at 03:02
  • ..interestingly, just across a userAgent string like `Tiphone T67/1.0 Browser/wap2.0 Sync/SyncClient1.1 Profile/MIDP-2.0 Configuration/CLDC-1.1` .. – zack Mar 01 '13 at 15:48
  • 26
    This is a better way to write your check: `var iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);` – LandonSchropp May 06 '13 at 05:59
  • 5
    Just a note - the navigator.platform array doesn't work on the iPad Simulator because it has the entire phrase "iPad Simulator" in the platform string. – Kevin Newman Jan 10 '14 at 05:39
  • 3
    Testing for iPad, iPhone or iPod in the user agent string will give a false positive in case the user has a Window Phone. Internet Explorer on this device contains a message '...like iPhone OS...' in the user agent string and thus will return true on this test. – Bas Slagter Aug 22 '14 at 09:02
  • 1
    This is much better `var iOS = /(iP*)/g.test(navigator.userAgent);` – VenomVendor Sep 13 '14 at 17:38
  • 1
    Why aren't you a fan of User Agent sniffing? – Marty Cortez Nov 21 '14 at 19:38
  • 2
    Simplest answer: var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent); – Sarsaparilla Jan 22 '15 at 21:48
  • 1
    Just another spanner to throw into the works... Crazy as it seems, (at least some) IE11 Mobile phones include iPhone in their UA. – Peter O'Callaghan Jul 31 '15 at 11:45
  • 1
    Thanks for the info Peter! I've done some research and updated the answer accordingly! – Pierre Jul 31 '15 at 19:11
  • Is there a way to infer iOS 9 yet? – dzh Sep 01 '15 at 03:48
  • 1
    @Dzh well in theory you can. It looks like iOS 9 will support forceTouch events.. If so, then you can try and test `'webkitmouseforcechanged' in document;`.. I don't have access to it yet, so if you are able to test this code let me know to update my answer. – Pierre Sep 01 '15 at 11:05
  • @Pierre good idea, but I wonder whether that will only detect iPhone 6S (as I would expect force touch events will only be available on the devices that support them)? – dzh Sep 03 '15 at 01:34
  • Aah then we need to find another feature, OS related rather than Device related.. – Pierre Sep 04 '15 at 08:43
  • 1
    [MDN](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorID/platform) says that `navigator.platform` is deprecated and not to rely on it. – Dan Feb 02 '16 at 08:27
  • Well spotted @Dan! I've updated the answer accordingly. Cheers – Pierre Feb 03 '16 at 20:09
  • @Pierre your code is superb (truely; respect) and I rolled it into some other `detection` code i've been using in prod, resulting in a great/lightweight gist for modern UI/UX control - **isMobile, isTablet, isIE, isIOS** -https://gist.github.com/gdibble/df3fd4016e632344336f3227a6f159e6 – gdibble May 20 '16 at 16:04
  • On a Mac mini I have this string (maybe is helpful for someone that needs to upgrade the regex): ```navigator.userAgent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"``` – Ferie Jun 07 '17 at 11:07
  • var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; - this might not work when minifying your js. Use this instead - var iOS = new RegExp("/iPad|iPhone|iPod/").test(navigator.userAgent) && !window.MSStream; – Yulian Jun 20 '17 at 13:49
  • @Pierre @Dan `navigator.platform` is not deprecated, it's just not recommended to rely on it. – cspanring Oct 06 '17 at 18:20
  • why is Microsoft literally always the worst – jessepinho Mar 04 '19 at 10:44
  • Please add "iPod touch" to the device list!! Other than that, perfect solution for my use case! – deleb Apr 29 '19 at 23:03
  • You are not checking whether it's an iOS device, but whether the device is either an iPhone, iPod or an iPad. If a new device with iOS were to be created, or if an Apple device is running another OS, then this will obviously not work. It's like detecting Windows by checking if the device is Microsoft Surface. – undefined Jun 23 '19 at 13:12
  • 16
    From iOS 13 the iPad's user agent has changed to "Mac OS", for example: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15` so this answer need to be updated – zvi Jul 07 '19 at 14:34
  • Not working for iOS 13. navigator.userAgent returns value of indexOf("ipad") as -1; – Sandip Jadhav Nov 18 '19 at 15:32
  • platform not deprecated (no red border) and less messy than userAgent. You can use `navigator.platform.indexOf('iP')>=0` or `navigator.userAgent.indexOf("(iP")>=0` which seemed to cover 99% of cases (googled useragent and platform strings). And nobody develops to be compatible with 100% of all browsers anyway. – Simon B. Jul 15 '20 at 09:37
  • @paul-rumkin Your last edit makes it NOT work for iOS 13 iPads. Please see the links right above what you edited. – Simon B. Jul 16 '20 at 23:30
  • @PaulRumkin thanks, but there were more necessary fixes in my rejected edit. Please at least fix this inconsistency: `function iOS()` (from someone elses edit) but then follows `iOS will be either true or false` (from original author). Also, the edit that changed from var to function forgot to change the text above the edit from `the shortest alternative` to something like `the most easy to read and edit` – Simon B. Jul 20 '20 at 19:55
  • Finally, this bit `if (iOS) { // – Simon B. Jul 20 '20 at 20:03
  • @gcharita The edit you rejected yesterday was to fix two glaring bugs in the answer. Please can you fix the bug instead? I've tried and gotten rejected. Otherwise I can try hunting down the original author on social media perhaps. I just want this to be not wrong. – Simon B. Oct 02 '20 at 09:20
  • This bit `(navigator.userAgent.includes("Mac") && "ontouchend" in document)` as recommended both here and elsewhere on StackOverflow page is no longer enough, since it returns true on Chrome 85 on a non touch device. Tacking on `&& navigator.maxTouchPoints > 2` seems like a good enough stop-gap fix for now. – Simon B. Oct 05 '20 at 10:42
53

After iOS 13 you should detect iOS devices like this, since iPad will not be detected as iOS devices by old ways (due to new "desktop" options, enabled by default):

let isIOS = /iPad|iPhone|iPod/.test(navigator.platform)
|| (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)

The first condition for iOS < 13 or iPhone or iPad with disabled Desktop mode, the second condition for iPadOS 13 in the default configuration, since it position itself like Macintosh Intel, but actually is the only Macintosh with multi-touch.

Rather a hack than a real solution, but work reliably for me

P.S. As being said earlier, you probably should add IE checkup

let isIOS = (/iPad|iPhone|iPod/.test(navigator.platform) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
!window.MSStream
kikiwora
  • 1,514
  • 8
  • 7
  • Why not use the `navigator.userAgent` for this check `/iPad|iPhone|iPod/.test(navigator.platform)`? It seems that `navigator.platform` always returns 'MacIntel' for iPhone iOS <= 12 – Charis Theo Oct 29 '19 at 14:32
  • @CharisTheo Because iPad is not in the userAgent in iOS >= 13 – Kzrbill Dec 20 '19 at 09:35
  • but you are already checking for iPad iOS >= 13 in the second check or am I missing something? – Charis Theo Dec 20 '19 at 16:48
  • `navigator.maxTouchPoints` isn't supported in iOS, so that check isn't going to do anything for you. – PaulC May 25 '20 at 18:01
  • @PaulC, You are correct in that maxTouchPoints is undefined for iOS 12 and below, but kikiwora is on the right track since maxTouchPoints is supported in iOS 13. See my answer. – Bob Arlof May 29 '20 at 21:28
  • Curious if there is a similar PHP version of this? Can PHP handle the maxTouchPoints? – drooh Jan 20 '21 at 17:41
  • @drooh I know not enough about PHP to be sure, but isn't it used for server-side logic? As far as I know, PHP was never executed in browsers. Thus, it would have no way to detect a page's runtime properties, only those that are passed during requests. Furthermore, the server should not ever know about the view layer, that's a responsibility of view, in our case - the page itself, not of a server that forms that page. Again, I may be mistaken about PHP, since I have had very limited experience with it. – kikiwora Jan 22 '21 at 09:06
14

This sets the variable _iOSDevice to true or false

_iOSDevice = !!navigator.platform.match(/iPhone|iPod|iPad/);
Vitim.us
  • 16,145
  • 12
  • 81
  • 96
10

If you are using Modernizr, you can add a custom test for it.

It doesn't matter which detection mode you decide to use (userAgent, navigator.vendor or navigator.platform), you can always wrap it up for a easier use later.

//Add Modernizr test
Modernizr.addTest('isios', function() {
    return navigator.userAgent.match(/(iPad|iPhone|iPod)/g);
});

//usage
if (Modernizr.isios) {
    //this adds ios class to body
    Modernizr.prefixed('ios');
} else {
    //this adds notios class to body
    Modernizr.prefixed('notios');
}
Craig Conover
  • 4,364
  • 29
  • 53
ThiagoPXP
  • 4,952
  • 3
  • 29
  • 43
  • 2
    Be carefull, Modernizr automatically lowercase the name of the added test. (in your example, Modernizr.isiOS will never return true). Bad behavior of the lib in my view ... – Jscti Nov 05 '14 at 10:04
  • 3
    Just tiny notice: you can simplify `return x ? true : false` to `return Boolean(x)` or just `return !!x` – tibalt Mar 07 '16 at 10:21
9

None of the previous answers here work for all major browsers on all versions of iOS, including iOS 13. Here is a solution that works for Safari, Chrome and Firefox for all iOS versions:

var isIOS = (function () {
    var iosQuirkPresent = function () {
        var audio = new Audio();

        audio.volume = 0.5;
        return audio.volume === 1;   // volume cannot be changed from "1" on iOS 12 and below
    };

    var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
    var isAppleDevice = navigator.userAgent.includes('Macintosh');
    var isTouchScreen = navigator.maxTouchPoints >= 1;   // true for iOS 13 (and hopefully beyond)

    return isIOS || (isAppleDevice && (isTouchScreen || iosQuirkPresent()));
})();

Note that this code snippet was written with priority on readability, not conciseness or performance.

Explanation:

  • If the user agent contains any of "iPod|iPhone|iPad" then clearly the device is iOS. Otherwise, continue...

  • Any other user agent that does not contain "Macintosh" is not an Apple device and therefore cannot be iOS. Otherwise, it is an Apple device, so continue...

  • If maxTouchPoints has a value of 1 or greater then the Apple device has a touch screen and therefore must be iOS since there are no Macs with touch screens (kudos to kikiwora for mentioning maxTouchPoints). Note that maxTouchPoints is undefined for iOS 12 and below, so we need a different solution for that scenario...

  • iOS 12 and below has a quirk that does not exist in Mac OS. The quirk is that the volume property of an Audio element cannot be successfully set to any value other than 1. This is because Apple does not allow volume changes on the Audio element for iOS devices, but does for Mac OS. That quirk can be used as the final fallback method for distinguishing an iOS device from a Mac OS device.

Bob Arlof
  • 740
  • 8
  • 16
  • It seems like this will have the side effect of actually changing the audio volume on non-iOS devices (in case that's important to anyone) – Tspoon Jul 03 '20 at 15:58
  • 1
    @Tspoon, The provided code snippet creates a throw-away `Audio` element (if necessary). The element is not actually used to play sound in this case, and it does not affect the volume of other `Audio` elements you might employ in your system. – Bob Arlof Jul 04 '20 at 16:54
7

A simplified, easy to extend version.

var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
Kory Nunn
  • 211
  • 3
  • 4
4

UPDATE: My original answer doesn't cover iPad in desktop mode (the default changes to desktop mode in upcoming iPadOS 13 and higher).
That's fine for my usecases, if it's not for you, use this update:

// iPhone and iPad including iPadOS 13+ regardless of desktop mode settings

iOSiPadOS = /^iP/.test(navigator.platform) ||
           /^Mac/.test(navigator.platform) && navigator.maxTouchPoints > 4;
  • This should be safe as long as
    • desktop Macs don't support touch events at all
    • or not more than 4 touch points (current iOS devices support 5 touch points)
  • It's fast because the regexp ^ first checks the starting position of the platform string and stops if there is no "iP" (faster than searching the long UA string until the end anyway)
  • It's safer than navigator.userAgent check as navigator.platform is much less likely faked
  • Detects iPhone / iPad Simulator

ORIGINAL ANSWER:
Wow, a lot of longish tricky code here. Keep it simple, please!

This one is IMHO fast, save, and working well:

 iOS = /^iP/.test(navigator.platform);

  // or, if you prefer it verbose:
 iOS = /^(iPhone|iPad|iPod)/.test(navigator.platform);
j.j.
  • 1,261
  • 9
  • 11
  • ```iOS = /^(iPhone|iPad|iPod)/.test(navigator.platform);``` rather than this i would do ```iOS = /^(iPhone|iPad|iPod)/.test(navigator.userAgent || navigator.vendor || navigator.platform);``` as a fallback measure cuz in my case navigator.platform didn't work, but doing it like later worked fine – Coderboi Sep 19 '19 at 16:20
  • 1
    `navigator.platform` didn't work? Are you really on iOS then?. Check with http://jeka.info/test/navigator.html . `userAgent` gives false positives because some vendors fake it to mimic Apple devices for whatever reasons. `vendor` just returns either `Google Inc.`, `Apple Computer, Inc.`, or nothing (in Firefox). – j.j. Oct 10 '19 at 18:00
4

It's probably worth answering that iPads running iOS 13 will have navigator.platform set to MacIntel, which means you'll need to find another way to detect iPadOS devices.

Justin Searls
  • 4,751
  • 4
  • 40
  • 56
3

Detecting iOS (both <12, and 13+)

Community wiki, as edit queue says it is full and all other answers are currently outdated or incomplete.

const iOS_1to12 = /iPad|iPhone|iPod/.test(navigator.platform);

const iOS13_iPad = (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1));

const iOS1to12quirk = function() {
  var audio = new Audio(); // temporary Audio object
  audio.volume = 0.5; // has no effect on iOS <= 12
  return audio.volume === 1;
};

const isIOS = !window.MSStream && (iOS_1to12 || iOS13_iPad || iOS1to12quirk());
Simon B.
  • 2,013
  • 18
  • 28
2

I wrote this a couple years ago but i believe it still works:

if(navigator.vendor != null && navigator.vendor.match(/Apple Computer, Inc./) && navigator.userAgent.match(/iPhone/i) || (navigator.userAgent.match(/iPod/i))) 

    {

        alert("Ipod or Iphone");

    }

else if (navigator.vendor != null && navigator.vendor.match(/Apple Computer, Inc./) && navigator.userAgent.match(/iPad/i))  

    {

        alert("Ipad");

    }

else if (navigator.vendor != null && navigator.vendor.match(/Apple Computer, Inc./) && navigator.userAgent.indexOf('Safari') != -1)

    {

        alert("Safari");

    }

else if (navigator.vendor == null || navigator.vendor != null)

    {

        alert("Not Apple Based Browser");

    }
Michael Benin
  • 3,949
  • 2
  • 21
  • 14
2

Wherever possible when adding Modernizr tests you should add a test for a feature, rather than a device or operating system. There's nothing wrong with adding ten tests all testing for iPhone if that's what it takes. Some things just can't be feature detected.

    Modernizr.addTest('inpagevideo', function ()
    {
        return navigator.userAgent.match(/(iPhone|iPod)/g) ? false : true;
    });

For instance on the iPhone (not the iPad) video cannot be played inline on a webpage, it opens up full screen. So I created a test 'no-inpage-video'

You can then use this in css (Modernizr adds a class .no-inpagevideo to the <html> tag if the test fails)

.no-inpagevideo video.product-video 
{
     display: none;
}

This will hide the video on iPhone (what I'm actually doing in this case is showing an alternative image with an onclick to play the video - I just don't want the default video player and play button to show).

Simon_Weaver
  • 120,240
  • 73
  • 577
  • 618
  • iOS10 now allows for `playsinline` so you can use `'playsInline' in document.createElement('video');` as a test now https://github.com/Modernizr/Modernizr/issues/2077 – Simon_Weaver Sep 30 '16 at 20:21
1

The user-agents on iOS devices say iPhone or iPad in them. I just filter based on those keywords.

Bryan Naegele
  • 660
  • 3
  • 10
0

Slightly update the first answer using a more functional approach.

    const isIOS = [
      'iPad Simulator',
      'iPhone Simulator',
      'iPod Simulator',
      'iPad',
      'iPhone',
      'iPod',
    ].indexOf(navigator.platform) !== -1;
Simon B.
  • 2,013
  • 18
  • 28
Sten Muchow
  • 6,373
  • 4
  • 33
  • 46
0

You can also use includes

  const isApple = ['iPhone', 'iPad', 'iPod', 'iPad Simulator', 'iPhone Simulator', 'iPod Simulator',].includes(navigator.platform)
Alan
  • 4,576
  • 25
  • 43
-1

In my case the user agent was not good enought since in the Ipad the user agent was the same as in Mac OS, therefore I had to do a nasty trick:

var mql = window.matchMedia("(orientation: landscape)");

/**
 * If we are in landscape but the height is bigger than width
 */
if(mql.matches && window.screen.height > window.screen.width) {
    // IOS
} else {
    // Mac OS
}
Ian Farré
  • 21
  • 5
-2

var isiOSSafari = (navigator.userAgent.match(/like Mac OS X/i)) ? true: false;

Mithun Sreedharan
  • 45,549
  • 69
  • 171
  • 232
-3

In order to detect the iOS version, one has to destructure the user agent with a Javascript code like this:

 var res = navigator.userAgent.match(/; CPU.*OS (\d_\d)/);
    if(res) {
        var strVer = res[res.length-1];
        strVer = strVer.replace("_", ".");
        version = strVer * 1;
    }
viebel
  • 13,724
  • 10
  • 43
  • 78