移动端判断设备是iphone/iPad/Mac

2023-01-04  本文已影响0人  CodeMT

一、问题描述

App 中有一个 H5 页面,在 iPhone 上展示的很好,在 iPadMini2 上也没问题。但是在 iPad Air 2 以及 iPad Pro 上,顶部会额外展示用户信息栏,就像进入了 PC 网站一样。
通过排查,最终找到了元凶——userAgent

iPad iOS13.5,Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15iPad iOS12.4.6,Mozilla/5.0 (iPad; CPU OS 12_4_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1iPad Mini iOS13.1.3Mozilla/5.0 (iPad; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1

从上面可以看到,从 iOS13 开始,iPad 设备获取的 userAgent 值中不再包含“iPad”字段了,而变成了“Macintosh”字段,不过奇怪的是,同样是 iOS13 的 iPad Mini 设备获取的 userAgent 值中却仍然有“iPad”字段。

二、起因

WWDC2019 苹果发布了 iOS13 和 iPad OS,从此 iPad 有了自己的操作系统。
随着 iPad 的尺寸越来越大,苹果为了更好的用户体验,一屏幕内可以展示更多的内容,在 iOS13 中,苹果改进 Safari,新增了一项功能——“请求桌面网站”。

通俗地讲,就是为了让 iPad 用起来像 PC。而且这个功能还“默认”开启,通过 iPad 设备打开 Safari 进入网站时会访问 PC 网站,而不是移动端网站。

那么怎么实现这个功能呢?聪明的苹果工程师想了个好办法——改 userAgent 值不就完了吗?假装自己是 PC 骗过所有人,完美,就这么干!


如何在 Safari 上禁用这个功能?

有个开关:设置-Safari-请求桌面网站,在 iOS13 的 iPad 设备(iPad Mini 除外)上这个开关默认是开启的,把它关闭就可以禁用这个功能了。

这个开关关闭后 Safari 上获取 userAgent 的值就恢复成原来的样子了。其实,iOS13 的 iPhone 上也有这个开关,只是默认是关闭的。

注意:Safari 上的这个开关只影响 Safari,不影响 WKWebView。即使你关闭了这个开关,App 内部创建的 WKWebView 仍然是默认值。

三、WKWebView 新增 API

如果我们想让 App 内部的 WKWebView 也回退到原来的 userAgent,该如何做呢?

iOS13,WKWebView 新增了几个和“请求桌面网站”相关的 API。

1. WKWebViewConfiguration 新增属性 defaultWebpagePreferences

@interface WKWebViewConfiguration : NSObject @property (null_resettable, nonatomic, copy) WKWebpagePreferences *defaultWebpagePreferences API_AVAILABLE(macos(10.15), ios(13.0));@end

defaultWebpagePreferences 是 WKWebpagePreferences 类型的。而 WKWebpagePreferences 类目前只有一个 preferredContentMode 属性,是一个枚举值,它代表 webview 的展现形式,是 PC 网站还是移动端网站。

typedef NS_ENUM(NSInteger, WKContentMode) {    WKContentModeRecommended,    WKContentModeMobile,    WKContentModeDesktop} API_AVAILABLE(ios(13.0));WK_EXTERN API_AVAILABLE(macos(10.15), ios(13.0))@interface WKWebpagePreferences : NSObject@property (nonatomic) WKContentMode preferredContentMode API_AVAILABLE(ios(13.0));@end

从注释(下图)中我们可以看到,preferredContentMode 属性的默认值是 WKContentModeRecommended,而 WKContentModeRecommended 会根据设备不同分别进入移动网站和 PC 网站。iPhone 和 iPad mini 设备默认访问移动网站,iPad、Mac 设备默认访问 PC 网站。

2. WKNavigationDelegate 新增一个代理方法

@protocol WKNavigationDelegate @optional//以前的方法- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;//新增的方法- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction preferences:(WKWebpagePreferences *)preferences decisionHandler:(void (^)(WKNavigationActionPolicy, WKWebpagePreferences *))decisionHandler API_AVAILABLE(macos(10.15), ios(13.0));@end

新增的代理方法作用和以前的方法作用和用法一样,只是多了参数 WKWebpagePreferences。通过这样方法你可以实现特定的网页进入特定的显示模式(PC 网站\移动端网站)。
需要注意的是,如果实现了新的代理方法,以前的代理方法将不再被调用。

四、解决方案

App 方案

在创建 WKWebView 之前手动把显示模式改成移动端模式,让 userAgent 回退到包含"iPad"字段的版本,这样前端就不会把 iPad 误判成 PC 了。

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];if (@available(iOS 13.0, *)) {   configuration.defaultWebpagePreferences.preferredContentMode = WKContentModeMobile;}

这也是苹果在WWDC2019[1]中推荐的方案。不过这个方案只解决 App 中的问题,如果用户在 Safari 或者公众号等环境访问你的 H5 页面,仍然面临这个问题。

前端方案

方案一

/*  * ipad环境判断更新  * iOS pre 13 以前以ua作判断,13后以platform及maxTouchPoints做判断  */
isiPad = (navigator.userAgent.match(/(iPad)/) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1))

iOS13 以前 navigator.platform 返回"iPhone"或"iPad";iOS13 以后的 iPad,navigator.platform 返回"MacIntel",相当于 Mac,由于目前还没有触摸屏的 Mac,所以可以通过最大触控点来区分 Mac 和 iPad 的。

当然这个方案并不严谨,说不定未来苹果就推出触摸屏的 Mac 了,但至少,不过此方法目前还是可行的

方案二

通过以下代码就可以完美判断出当前设备是否为iPad,成功率99%以上,利用到了iPad的一个特性来判断。iPad的屏幕高始终>屏幕宽(不管你将屏幕竖着还是横着),而Mac的宽>高

iPad 的UA和 屏幕高宽 MacBook的UA和屏幕高宽

以上这个特性用代码写出就是

/macintosh|mac os x/i.test(navigator.userAgent) && window.screen.height > window.screen.width

至于为什么后面要加

!navigator.userAgent.match(/(iPhone\sOS)\s([\d_]+)/))

是因为要剔除开了”请求桌面网站”的iPhone(iPhone符合了屏幕高>宽 且 开了请求桌面网站后UA里也携带了mac os)

最后再加上对老系统的iPad的判断,老系统iPad的UA里都有iPad标识符

navigator.userAgent.match(/(iPad).*OS\s([\d_]+)/)

至于为什么说成功率只有99%,不是100%,因为这个判断有一种究极的可复现的BUG:

用着MacBook 并且买了一个可以竖置的显示器 并且将显示器竖着浏览你的网页,这样UA不仅包含了”mac os” 而且屏高还>屏宽,但是这种情况我相信应该不多

五、总结

上一篇下一篇

猜你喜欢

热点阅读