73

I have an app, and I'd like to redirect the users to different pages based on where they are navigating from.

If navigating from web clip, do not redirect. If navigating from mobile Safari, redirect to safari.aspx. If navigating from anywhere else, redirect to unavailable.aspx

I was able to use iPhone WebApps, is there a way to detect how it was loaded? Home Screen vs Safari? to determine if the user was navigating from a web clip, but I'm having trouble determining if the user navigated from mobile Safari on an iPhone or iPod.

Here's what I have:

if (window.navigator.standalone) {
    // user navigated from web clip, don't redirect
}
else if (/*logic for mobile Safari*/) {
    //user navigated from mobile Safari, redirect to safari page
    window.location = "safari.aspx";
}
else {
    //user navigated from some other browser, redirect to unavailable page
    window.location = "unavailable.aspx";
}
Community
  • 1
  • 1
Steven
  • 16,943
  • 64
  • 174
  • 272

13 Answers13

167

See https://developer.chrome.com/multidevice/user-agent#chrome_for_ios_user_agent - the user agent strings for Safari on iOS and for Chrome on iOS are inconveniently similar:

Chrome

Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3

Safari

Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3

Looks like the best approach here is to first of all check for iOS as other answers have suggested and then filter on the stuff that makes the Safari UA unique, which I would suggest is best accomplished with "is AppleWebKit and is not CriOS":

var ua = window.navigator.userAgent;
var iOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i);
var webkit = !!ua.match(/WebKit/i);
var iOSSafari = iOS && webkit && !ua.match(/CriOS/i);
Kamil Kiełczewski
  • 53,729
  • 20
  • 259
  • 241
unwitting
  • 3,138
  • 2
  • 16
  • 19
  • 1
    @fabricioflores c'est la vie :( – unwitting Apr 24 '15 at 10:34
  • 7
    +1 The only answer here that covered all the specs. I would just change the iOS test regex to `/iP(ad|hone)/i` so you don't need the or. – sareed Oct 08 '15 at 15:04
  • @sareed don't forget iPod – JoshuaDavid Oct 29 '15 at 00:08
  • 2
    @sareed I always found it astoundingly satisfying to be able to regex iP(ad|od|hone) – unwitting Oct 30 '15 at 10:31
  • 8
    Stupid Opera on iOS would still pass true on the code above, so I changed the last line to this: var iOSSafari = iOS && webkit && !ua.match(/CriOS/i) && !ua.match(/OPiOS/i); – Hooman Askari Nov 16 '15 at 10:27
  • 3
    Nice answer. But why using a double negative (!!) with match when you can use test()? – OMA Jan 18 '16 at 18:06
  • @OMA no reason. Just an arbitrary choice for an example where performance isn't really a consideration :) – unwitting Jan 19 '16 at 18:16
  • 1
    Simulating IPhone with Chrome desktop, it returns something like _Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Mobile Safari/537.36_. I would extend the already provided answer to `var iOSSafari = iOS && webkit && !/(Chrome|CriOS|OPiOS)/.test(ua)` – Miguel Guardo May 05 '17 at 16:55
  • 1
    @HumanA. and in order to exclude Microsoft Edge on iOS, I added !ua.match(/EdgiOS/i), so the full version looks like this: var iOSSafari = iOS && webkit && !ua.match(/CriOS/i) && !ua.match(/OPiOS/i) && !ua.match(/EdgiOS/i); – Sergey Jun 26 '18 at 11:15
  • Mobile chrome is still passing this check – bumbeishvili Dec 13 '18 at 15:28
  • can confirm this works, and so is: ```!!window.navigator.userAgent.match(/iPhone/i) && !!window.navigator.userAgent.match(/Safari/i)``` – halafi Sep 25 '19 at 10:09
  • that it shouldn't have `CriOS` is not enough to determine that it is indeed Safari. It only tells us it's not Chrome. Firefox on iOS has `FxiOS` instead – Loonie Oct 10 '19 at 20:50
  • This doesn't work for me. – kyleplo Feb 21 '21 at 20:13
33

UPDATE: This is a very old answer and I cannot delete it because the answer is accepted. Check unwitting's answer below for a better solution.


You should be able to check for the "iPad" or "iPhone" substring in the user agent string:

var userAgent = window.navigator.userAgent;

if (userAgent.match(/iPad/i) || userAgent.match(/iPhone/i)) {
   // iPad or iPhone
}
else {
   // Anything else
}
Community
  • 1
  • 1
Daniel Vassallo
  • 312,534
  • 70
  • 486
  • 432
  • 11
    I think it's not. How about different than safari iOS browsers? I think this expression will exclude not only Safari, but all iphone/ipad browsers. – Kamilius Mar 31 '14 at 10:38
  • 17
    this is not the right answer because if I access it from chrome for ios it will return true – Bobby Stenly Jan 14 '15 at 11:09
  • 7
    This doesn't work, because the user could be browsing with Mobile Chrome on iOS, now, and this would still return true even though it's not Mobile Safari. – jangosteve Jan 19 '15 at 17:04
  • 2
    This does not detect if the user is for example using mobile Chrome. And Chrome iOS has a very ancient javascript engine (due to Apple's policy) in comparison to Safari, making some rendering impossible. – Aero Wang Mar 24 '15 at 20:57
  • It does include iOS Chrome, but since iOS Chrome is just Safari with a different UI, for all intents and purposes this is fine. – Gio Dec 09 '15 at 10:30
  • 3
    This shouldn't be marked a correct answer as it includes any browser running on iPhone. The question specifically ask to detect mobile safari. – Shashank Jun 21 '17 at 05:35
27

best practice is:

function isMobileSafari() {
    return navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/)
}
appsthatmatter
  • 6,201
  • 3
  • 32
  • 39
8

Falling code only find mobile safari and nothing else (except dolphin and other small browsers)

  (/(iPad|iPhone|iPod)/gi).test(userAgent) &&
  !(/CriOS/).test(userAgent) &&
  !(/FxiOS/).test(userAgent) &&
  !(/OPiOS/).test(userAgent) &&
  !(/mercury/).test(userAgent)
aWebDeveloper
  • 31,131
  • 35
  • 150
  • 224
6

Merged all the answers and comments. And this is the result.

function iOSSafari(userAgent)
{
    return /iP(ad|od|hone)/i.test(userAgent) && /WebKit/i.test(userAgent) && !(/(CriOS|FxiOS|OPiOS|mercury)/i.test(userAgent));
}



var iOSSafari = /iP(ad|od|hone)/i.test(window.navigator.userAgent) && /WebKit/i.test(window.navigator.userAgent) && !(/(CriOS|FxiOS|OPiOS|mercury)/i.test(window.navigator.userAgent));
5

Seeing all the answers, here are some tips about the proposed RegExes:

  • AppleWebKit matches Desktop Safari too (not only mobile)
  • no need to call .match more than once for such simple regexes, and prefer the lighter .test method.
  • the g (global) regex flag is useless while the i (case insensitive) can be useful
  • no need for capture (parenthesis), we are just testing the string

I'm just using this since getting true for mobile Chrome is OK for me (same behavior):

/iPhone|iPad|iPod/i.test(navigator.userAgent)

(I just want to detect if the device is a target for an iOS app)

antoine129
  • 4,484
  • 4
  • 29
  • 50
  • 2
    Important remarks about the regular expressions (in general), thanks for pointing this out here (where most got it wrong)! In addition to your answer, one should nowadays check for `!window.MSStream` as well to exclude Microsoft’s browsers as per [this answer](https://stackoverflow.com/a/9039885/89818) to another question. And `!/CriOS/.test(navigator.userAgent)` is for those who want Chrome filtered out as per [this answer](https://stackoverflow.com/a/13808053/89818). – caw Feb 04 '18 at 06:45
1

Actually, there isn't a silver bullet of detecting mobile safari. There are quite a few browsers may use the keywords of the user agent of mobile safari. Maybe you can try feature detection and keep updating the rule.

Light
  • 464
  • 4
  • 15
  • 1
    No in my case, for example. I want to show custom smart banner made for iOS+Chrome, but not show this banner on iOS+Safari, because iOS+Safari already have a built-in smart banner. – JobaDiniz Jul 14 '15 at 15:07
  • @JobaDiniz I know many people want to do something just in mobile safari but not in other browers, so do I. However, as a matter of fact, browsers which match tokens like "iPad", "iOS", "iPhone" .etc don't mean they are mobile safari, maybe they are some other browsers for iOS. For example, UC and QQ browsers in China. All these browsers may use the core of safari and most behaviors are the same as mobile safari, but some not. It sucks. – Light Jul 15 '15 at 03:39
  • It sucks indeed. Foolish of me I had hoped that navigator.appname would be different, all iOS browsers are 'Netscape'. Yet, Safari is the only one capable of installing Configuration Profiles, so if any other gets a .mobileconfig, it download it and looks stupid because it does not know which app can open it... – Mitch Match Nov 07 '15 at 09:19
1

I upvoted @unwitting 's answer, as it inevitably got me going. However, when rendering my SPA in an iOS Webview, I needed to tweak it a bit.

function is_iOS () {
    /*
        Returns (true/false) whether device agent is iOS Safari
    */
    var ua = navigator.userAgent;
    var iOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i);
    var webkitUa = !!ua.match(/WebKit/i);

    return typeof webkitUa !== 'undefined' && iOS && webkitUa && !ua.match(/CriOS/i);
};

The main difference being, the renaming of webkit to webkitUa, so as to prevent clashing with the root webkit object used as a message handler between the SPA & UIView.

Community
  • 1
  • 1
pim
  • 10,145
  • 4
  • 59
  • 61
1

I know this is an old thread, but I'd like to share my solution with you guys.

I needed to detect when an user navigates from Desktop Safari (Because we're in middle 2017, and Apple hasn't give any support for input[type="date"] YET...

So, I made a fallback custom datepicker for it) . But only applies to safari in desktop because that input type works fine in mobile Safari. So, I made this Regex to only detect desktop Safari. I already tested it and it doesn't match with Opera, Chrome, Firefox or Safari Mobile.

Hope it may help some of you guys.

if(userAgent.match(/^(?!.*chrome).(?!.*mobile).(?!.*firefox).(?!.*iPad).(?!.*iPhone).*safari.*$/i)){
  $('input[type="date"]').each(function(){
    $(this).BitmallDatePicker();
  })
}
edd2110
  • 316
  • 1
  • 9
1

this regex works for me, clean and simple

const isIOSSafari = !!window.navigator.userAgent.match(/Version/[\d.]+.*Safari/);

cyianite
  • 31
  • 1
0
function isIOS {
  var ua = window.navigator.userAgent;
  return /(iPad|iPhone|iPod).*WebKit/.test(ua) && !/(CriOS|OPiOS)/.test(ua);
}
Kugutsumen
  • 706
  • 6
  • 15
0

I was looking for this answer and I remembered I came across this before.

The most reliable way to detect Safari on iOS in JavaScript is

if (window.outerWidth === 0) {
    // Code for Safari on iOS
} 

or 

if (window.outerHeight === 0) {
    // Code for Safari on iOS
} 

For some reason Safari on iOS returns 0 for window.outerHeight property and window.outerWidth property.

This is for all iPads and iPhones on all versions of iOS. Every other browser and device this property works normally.

Not sure if they intend to change this but for now it works well.

naeluh
  • 751
  • 8
  • 16
0
var isApple = false;    
if(/iPhone|iPad|iPod/i.test(navigator.userAgent)) {      
  isApple = true;
}
Diogo Souza
  • 280
  • 1
  • 3
  • 7
  • While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn, and apply that knowledge to their own code. You are also likely to have positive feedback from users in the form of upvotes, when the code is explained. – borchvm Jan 14 '21 at 07:30