iOS13 苹果“偷摸地“改了iPad的userAgent,你被坑过吗

18,019 阅读6分钟

一、奇怪的bug

最近App需要支持iPad,在开发过程中遇到了一个奇怪的bug。
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.15  

iPad 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.1 

iPad Mini iOS13.1.3
Mozilla/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 <NSSecureCoding, NSCopying>

@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 <NSObject>
@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网站\移动端网站)。
需要注意的是,如果实现了新的代理方法,以前的代理方法将不再被调用。

四、解决方案

1. App方案

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

OC版本

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

Swift版本

let configuration = WKWebViewConfiguration()
if #available(iOS 13.0, *) {
   configuration.defaultWebpagePreferences.preferredContentMode = .mobile
}
webView = WKWebView.init(frame: .zero, configuration: configuration)

这也是苹果在WWDC2019中推荐的方案。
不过这个方案只解决App中的问题,如果用户在Safari或者公众号(微信、QQ等环境我测了下没问题,可能他们已经适配了这个属性了吧)等环境访问你的H5页面,仍然面临这个问题WWDC2019

2. 前端方案

/*
  * 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了。我在网上也没有找到最佳解决方案,但至少,目前来看这个方法能解决问题。如果你有更好的方案,欢迎分享。

五、总结

  • iOS13苹果为了提升iPad的用户体验,让用户能够在移动端上访问PC网页,新增了一个“请求桌面网站”的功能,当这个功能开启后,浏览器获取的userAgent的值将变成Mac,从而“欺骗”浏览器达到访问PC网站的目的。
  • “请求桌面网站”这个功能的开关分为Safari和App内的WKWebView两处,它们两个是独立的,相互不影响的。对于Safari,可以通过 设置-Safari-请求桌面网站 中的开关来控制这个功能;对于WKWebView可以通过webViewConfiguration.defaultWebpagePreferences属性或者新增的代理方法来控制这个功能。
  • 对于iOS13的系统,无论是Safari还是WKWebView,它们的默认值的表现是一样的——iPhone和iPad mini设备,“请求桌面网站”的功能默认是关闭的,UA为"iPhone"或"iPad",访问移动网站;iPad设备,“请求桌面网站”的功能默认是开启的,UA为"Mac",访问PC网站

说白了,就是苹果在iOS13上“偷摸地”改了iPad的userAgent的默认值,挖了这么个大个坑。


如果觉得这篇文章对你有帮助,请点个赞吧。欢迎关注我的公众号
转载请注明出处,谢谢!

参考链接:
WKWebView 在 iOS13 iPadOS 获取到的UserAgent中的设备变成Macintosh的问题
WWDC2019:Introducing Desktop-class Browsing on iPad
User Agent in Safari on iPadOS
How to detect iPad and iPad OS version in iOS 13 and Up?