iOS WKWebView适配实战篇

2025-05-29 0 86

一、cookie适配

1.现状

wkwebview适配中最麻烦的就是cookie同步问题

wkwebview采用了独立存储控件,因此和以往的uiwebview并不互通

虽然ios11以后,ios开放了wkhttpcookiestore让开发者去同步,但是还是需要考虑低版本的 同步问题,本章节从各个角度切入考虑cookie同步问题

2.同步cookie(nshttpcookiestorage->wkhttpcookiestore)

ios11+

可以直接使用wkhttpcookiestore遍历方式设值,可以在创建wkwebview时候就同步也可以是请求时候

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14
// ios11同步 httpcookiestorag到wkhttpcookiestore

wkhttpcookiestore *cookiestore = self.wkwebview.configuration.websitedatastore.httpcookiestore;

- (void)synccookiestowkcookiestore:(wkhttpcookiestore *)cookiestore api_available(ios(11.0)){

nsarray *cookies = [[nshttpcookiestorage sharedhttpcookiestorage] cookies];

if (cookies.count == 0) return;

for (nshttpcookie *cookie in cookies) {

[cookiestore setcookie:cookie completionhandler:^{

if ([cookies.lastobject isequal:cookie]) {

[self wkwebviewsetcookiesuccess];

}

}];

}

}

同步cookie可以在初始化wkwebview的时候,也可以在请求的时候。初始化时候同步可以确保发起html页面请求的时候带上cookie

例如:请求在线页面时候要通过cookie来认证身份,如果不是初始化时同步,可能请求页面时就是401了

ios11-

通过前端执行js注入cookie,在请求时候执行

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30
//wkwebview执行js

- (void)injectcookieslt11 {

wkuserscript * cookiescript = [[wkuserscript alloc] initwithsource:[self cookiestring] injectiontime:wkuserscriptinjectiontimeatdocumentstart formainframeonly:no];

[self.wkwebview.configuration.usercontentcontroller adduserscript:cookiescript];

}

//遍历nshttpcookiestorage,拼装js并执行

- (nsstring *)cookiestring {

nsmutablestring *script = [nsmutablestring string];

[script appendstring:@"var cookienames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\\n"];

for (nshttpcookie *cookie in nshttpcookiestorage.sharedhttpcookiestorage.cookies) {

// skip cookies that will break our script

if ([cookie.value rangeofstring:@"'"].location != nsnotfound) {

continue;

}

[script appendformat:@"if (cookienames.indexof('%@') == -1) { document.cookie='%@'; };\\n", cookie.name, [self formatcookie:cookie]];

}

return script;

}

//format cookie的js方法

- (nsstring *)formatcookie:(nshttpcookie *)cookie {

nsstring *string = [nsstring stringwithformat:@"%@=%@;domain=%@;path=%@",

cookie.name,

cookie.value,

cookie.domain,

cookie.path ?: @"/"];

if (cookie.secure) {

string = [string stringbyappendingstring:@";secure=true"];

}

return string;

}

但是上面方法执行js,也无法保证第一个页面请求带有cookie

所以请求时候创建request需要设置cookie,并且loadrequest

?

1

2

3

4

5

6

7

8

9

10

11

12
-(void)injectrequestcookielt11:(nsmutableurlrequest*)mutablerequest {

// ios11以下,手动同步所有cookie

nsarray *cookies = nshttpcookiestorage.sharedhttpcookiestorage.cookies;

nsmutablearray *mutablecookies = @[].mutablecopy;

for (nshttpcookie *cookie in cookies) {

[mutablecookies addobject:cookie];

}

// cookies数组转换为requestheaderfields

nsdictionary *requestheaderfields = [nshttpcookie requestheaderfieldswithcookies:(nsarray *)mutablecookies];

// 设置请求头

mutablerequest.allhttpheaderfields = requestheaderfields;

}

3.反向同步cookie(wkhttpcookiestore->nshttpcookiestorage)

wkwebview产生的cookie也可能在某些场景需要同步给nshttpcookiestorage

ios11+可以直接用wkhttpcookiestore去同步,

ios11-可以采用js端获取,触发bridge同步给nshttpcookiestorage

但是js同步方式无法同步httponly,所以真的遇到了,还是要结合服务器等方式去做这个同步。

二、js和native通信

1.native调用js

将代码准备完毕后调用api即可,回调函数可以接收js执行结果或者错误信息,so easy。

?

1
[self.wkwebview evaluatejavascript:jscode completionhandler:^(id object, nserror *error){}];

2.注入js

其实就是提前注入一些js方法,可以提供给js端调用。

比如有的框架会将bridge直接通过这种方式注入到wk的执行环境中,而不是从前端引入js,这种好处就是假设前端的js是在线加载,js服务器挂了或者网络问题,这样前端页面就失去了naitve的bridge通信能力了。

?

1

2

3

4

5

6

7
-(instancetype)initwithsource:(nsstring *)source injectiontime:(wkuserscriptinjectiontime)injectiontime formainframeonly:(bool)formainframeonly;

//wkuserscriptinjectiontime说明

typedef ns_enum(nsinteger, wkuserscriptinjectiontime) {

wkuserscriptinjectiontimeatdocumentstart, /**文档开始时候就注入**/

wkuserscriptinjectiontimeatdocumentend /**文档加载完成时注入**/

} api_available(macos(10.10), ios(8.0));

3.js调用native

3-1.准备代理类

代理类要实现wkscriptmessagehandler

?

1

2

3

4
@interface weakscriptmessagedelegate : nsobject<wkscriptmessagehandler>

@property (nonatomic, weak) id<wkscriptmessagehandler> scriptdelegate;

- (instancetype)initwithdelegate:(id<wkscriptmessagehandler>)scriptdelegate;

@end

wkscriptmessagehandler就一个方法

?

1

2

3

4

5

6

7

8

9

10

11

12
@implementation weakscriptmessagedelegate

- (instancetype)initwithdelegate:(id<wkscriptmessagehandler>)scriptdelegate {

self = [super init];

if (self) {

_scriptdelegate = scriptdelegate;

}

return self;

}

- (void)usercontentcontroller:(wkusercontentcontroller *)usercontentcontroller didreceivescriptmessage:(wkscriptmessage *)message {

[self.scriptdelegate usercontentcontroller:usercontentcontroller didreceivescriptmessage:message];

}

3-2.设置代理类

合适时机(一般初始化)设置代理类,并且指定name

?

1

2
nsstring* messagehandlername = @"bridge";

[config.usercontentcontroller addscriptmessagehandler:[[weakscriptmessagedelegate alloc] initwithdelegate:self] name:messagehandlername];

3-3.bridge的使用(js端)

执行完上面语句后就会在js端注入了一个对象"window.webkit.messagehandlers.bridge"

?

1

2
//js端发送消息,参数最好选用string,比较通用

window.webkit.messagehandlers.bridge.postmessage("type");

3-4.native端消息的接收

然后native端可以通过wkscriptmessage的body属性中获得传入的值

?

1

2

3

4

5

6

7
- (void)usercontentcontroller:(wkusercontentcontroller*)usercontentcontroller didreceivescriptmessage:(wkscriptmessage *)message{

if ([message.name isequaltostring:historybridagename]) {

} else if ([message.name isequaltostring:messagehandlername]) {

[self jstonativeimpl:message.body];

}

}

3-5.思考题

这里我们为什么要使用weakscriptmessagedelegate,并且再设置个delegate指向self(controller),为什么不直接指向?

提示:可以参考nstimer的循环引用问题

3-6.完整的示例

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36
-(void)_defaultconfig{

wkwebviewconfiguration* config = [wkwebviewconfiguration new];

…… ……

…… ……

wkusercontentcontroller* usercontroller = [[wkusercontentcontroller alloc] init];

config.usercontentcontroller = usercontroller;

[self injecthistorybridge:config];

…… ……

…… ……

}

-(void)injecthistorybridge:(wkwebviewconfiguration*)config{

[config.usercontentcontroller addscriptmessagehandler:[[weakscriptmessagedelegate alloc] initwithdelegate:self] name:historybridagename];

nsstring *_jssource = [nsstring stringwithformat:

@"(function(history) {\\n"

" function notify(type) {\\n"

" settimeout(function() {\\n"

" window.webkit.messagehandlers.%@.postmessage(type)\\n"

" }, 0)\\n"

" }\\n"

" function shim(f) {\\n"

" return function pushstate() {\\n"

" notify('other')\\n"

" return f.apply(history, arguments)\\n"

" }\\n"

" }\\n"

" history.pushstate = shim(history.pushstate)\\n"

" history.replacestate = shim(history.replacestate)\\n"

" window.addeventlistener('popstate', function() {\\n"

" notify('backforward')\\n"

" })\\n"

"})(window.history)\\n", historybridagename

];

wkuserscript *script = [[wkuserscript alloc] initwithsource:_jssource injectiontime:wkuserscriptinjectiontimeatdocumentstart formainframeonly:yes];

[config.usercontentcontroller adduserscript:script];

}

3-7.其它问题

在ios8 beta5前,js和native这样通信设置是不行的,所以可以采用生命周期中做url的拦截去解析数据来达到效果,这里不做赘述,可以自行参考网上类似uiwebview的桥接原理文章

三、实战技巧

1.useragent的设置

添加ua

实际过程中最好只是原有ua上做添加操作,全部替换可能导致服务器的拒绝(安全策略)

iOS WKWebView适配实战篇

日志中红线部分是整个模拟器的ua,绿色部门是ua中的applicationname部分

ios9上,wkwebview提供了api可以设置ua中的applicationname

?

1
config.applicationnameforuseragent = [nsstring stringwithformat:@"%@ %@", config.applicationnameforuseragent, @"arleneconfig"];

全部替换ua

ios9以上直接可以指定wkwebview的customuseragent,ios9以下的话,设置nsuserdefaults

?

1

2

3

4

5

6
if (@available(ios 9.0, *)) {

self.wkwebview.customuseragent = @"hello my useragent";

}else{

[[nsuserdefaults standarduserdefaults] registerdefaults:@{@"useragent":@"hello my useragent"}];

[[nsuserdefaults standarduserdefaults] synchronize];

}

2.监听进度和页面的title变化

wkwebview可以监控页面加载进度,类似浏览器中打开页面中的进度条的显示

页面切换的时候也会自动更新页面中设置的title,可以在实际项目中动态切换容器的title,比如根据切换的title设置navigationitem.title

原理直接通过kvo方式监听值的变化,然后在回调中处理相关逻辑

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28
//kvo 加载进度

[self.webview addobserver:self

forkeypath:@"estimatedprogress"

options:nskeyvalueobservingoptionold | nskeyvalueobservingoptionnew

context:nil];

//kvo title

[self.webview addobserver:self

forkeypath:@"title"

options:nskeyvalueobservingoptionnew

context:nil];

/** kvo 监听具体回调**/

- (void)observevalueforkeypath:(nsstring *)keypath ofobject:(id)object change:(nsdictionary<nskeyvaluechangekey,id> *)change context:(void *)context{

if ([keypath isequal:@"estimatedprogress"] && object == self.webview) {

allogf(@"progress--->%@",[nsnumber numberwithdouble:self.webview.estimatedprogress]);

}else if([keypath isequaltostring:@"title"]

&& object == self.webview){

self.navigationitem.title = self.webview.title;

}else{

[super observevalueforkeypath:keypath ofobject:object change:change context:context];

}

}

/**销毁时候记得移除**/

[self.webview removeobserver:self

forkeypath:nsstringfromselector(@selector(estimatedprogress))];

[self.webview removeobserver:self

forkeypath:nsstringfromselector(@selector(title))];

3.bridge通信实战

下面介绍自己实现的bridge通信框架,前端无需关心所在容器,框架层做适配

?

1

2

3

4

5

6

7

8

9

10

11

12

13
import {webbridge} from 'xxx'

/**

* 方法: webbridge.call(taskname,options,callback)

* 参数说明:

* taskname string task的名字,用于native处理分发任务的标识

* options object 传递的其它参数

* callback function 回调函数

*. 回调参数

* json object native返回的内容

**/

webbridge.call("alert",{"content":"弹框内容","btn":"btn内容"},function(json){

console.log("call back is here",json.stringify(json));

});

上面调用了native的alert控件,然后返回调用结果。

调用到的native代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23
//alerttask.m

#import "alerttask.h"

#import <lib-base/albaseconstants.h>

@interface alerttask (){}

@property (nonatomic,weak) arlenewebviewcontroller* mctrl;

@end

@implementation alerttask

-(instancetype)initwithcontext:(arlenewebviewcontroller*)controller{

self = [super init];

self.mctrl = controller;

return self;

}

-(nsstring*)taskname{

return @"alert";

}

-(void)dotask:(nsdictionary*)params{

alshowalert(@"title",@"message");//弹出alert

nsmutabledictionary* callback = [arlenetaskutils basiccallback:params];//获取callback

[callback addentriesfromdictionary:params];

[self.mctrl calljs:callback];//执行回调

}

@end

具体实现原理可以点击下方视频链接:

点击获取框架原理视频

到此这篇关于ios wkwebview适配实战篇的文章就介绍到这了,更多相关ios wkwebview适配 内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 iOS WKWebView适配实战篇 https://www.kuaiidc.com/89324.html

相关文章

发表评论
暂无评论