UIWebView -> WKWebView

非商业行为,为自己复习巩固

IOS 8之前使用UIWebView,IOS 8之后就出现了WKWebView,
相比UIWebView,WKWebView优化了较多.

  1. WKWebView的内存开销比UIWebView小很多
  2. 内置手势
  3. 支持了更多的HTML5特性
  4. 有Safari相同的JavaScript引擎
  5. 提供常用的属性如加载网页进度的estimatedProgress属性
UIWebView和WKWebView的流程.png

WKWebView的流程粒度更加细致

#请求数据的时候询问
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
#返回数据的时候询问
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

流程中,WKWebView返回的错误

#请求数据时发生的error
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
#请求之后加载H5发生的error
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;

WKWebView基本使用

使用WKWebView引用头文件

- (void)setupWebview{
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.selectionGranularity = WKSelectionGranularityDynamic;
    config.allowsInlineMediaPlayback = YES;
    WKPreferences *preferences = [WKPreferences new];
    //是否支持JavaScript
    preferences.javaScriptEnabled = YES;
    //不通过用户交互,是否可以打开窗口
    preferences.javaScriptCanOpenWindowsAutomatically = YES;
    config.preferences = preferences;
    WKWebView *webview = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight - 64) configuration:config];
    [self.view addSubview:webview];
     
    /* 加载服务器url的方法*/
    NSString *url = @"https://www.baidu.com";
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    [webview loadRequest:request];
     
    webview.navigationDelegate = self;
    webview.UIDelegate = self;
}

WKNavigationDelegate

#pragma mark - WKNavigationDelegate
/* 页面开始加载 */
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
}
/* 开始返回内容 */
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
     
}
/* 页面加载完成 */
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
     
}
/* 页面加载失败 */
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
     
}
/* 在发送请求之前,决定是否跳转 */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    //允许跳转
    decisionHandler(WKNavigationActionPolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationActionPolicyCancel);
}
/* 在收到响应后,决定是否跳转 */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
     
    NSLog(@"%@",navigationResponse.response.URL.absoluteString);
    //允许跳转
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationResponsePolicyCancel);
}

WKWebView细节

url 中文处理

加载的url中出现了中文需要手动转码,但同时又要保证URL中的特殊字符保持不变,那么我们可以使用下面的方法(方法)

- (NSURL *)url{
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
    return [NSURL URLWithString:(NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, 
                (CFStringRef)self, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", 
                NULL,kCFStringEncodingUTF8))];
#pragma clang diagnostic pop
}

获取h5中的标题以及添加进度条

获取h5中的标题和添加进度条 在初始化webView添加两个观察者分别用来监听webView的estimateProgress和title属性

webview.navigationDelegate = self;
webview.UIDelegate = self;
     
[webview addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
[webview addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];

添加创建进度条,并添加进度条图层属性:

@property (nonatomic,weak) CALayer *progressLayer;

-(void)setupProgress{
    UIView *progress = [[UIView alloc]init];
    progress.frame = CGRectMake(0, 0, KScreenWidth, 3);
    progress.backgroundColor = [UIColor  clearColor];
    [self.view addSubview:progress];
     
    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(0, 0, 0, 3);
    layer.backgroundColor = [UIColor greenColor].CGColor;
    [progress.layer addSublayer:layer];
    self.progressLayer = layer;
}

观察者的回调方法:

#pragma mark - KVO回馈
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<nskeyvaluechangekey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        self.progressLayer.opacity = 1;
        if ([change[@"new"] floatValue] <[change[@"old"] floatValue]) {
            return;
        }
        self.progressLayer.frame = CGRectMake(0, 0, KScreenWidth*[change[@"new"] floatValue], 3);
        if ([change[@"new"]floatValue] == 1.0) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                self.progressLayer.opacity = 0;
                self.progressLayer.frame = CGRectMake(0, 0, 0, 3);
            });
        }
    }else if ([keyPath isEqualToString:@"title"]){
        self.title = change[@"new"];
    }
}</nskeyvaluechangekey,id>

添加userAgent信息

h5的欧版需要我们为WebView的请求添加userAgent,来识别操作系统等信息,但如果每次用到webView都要添加一次会比较麻烦,下面是一种解决问题的办法

在Appdelegate中添加一个WKWebView的属性,启动app时直接为该属性添加userAgent:

- (void)setUserAgent {
    _webView = [[WKWebView alloc] initWithFrame:CGRectZero];
    [_webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
        if (error) { return; }
        NSString *userAgent = result;
        if (![userAgent containsString:@"/mobile-iOS"]) {
            userAgent = [userAgent stringByAppendingString:@"/mobile-iOS"];
            NSDictionary *dict = @{@"UserAgent": userAgent};
            [TKUserDefaults registerDefaults:dict];
        }
    }];
}

JS调用OC

js通过以下方法调用原生方法

window.webkit.messageHandlers.<#对象名#>.postMessage(<#参数#>)

原生中要实现WKScriptMessageHandler的代理方法,值得注意的是参数name需要与上述代码中对象名一致。

// 添加scriptMessageHandler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler 
         name:(NSString *)name;

然后在

- (void)userContentController:(WKUserContentController *)userContentController 
      didReceiveScriptMessage:(WKScriptMessage *)message;

在方法中获取做判断,响应对应的方法:

// 初始化WKWebView,在实例化WKWebViewConfiguration对象的时候我们同时添加scriptMessageHandler
 //进行配置控制器
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
//实例化对象
configuration.userContentController = [WKUserContentController new];
//调用JS方法
[configuration.userContentController addScriptMessageHandler:self name:@"btnClick"];

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"btnClick"]) {
        NSDictionary *jsData = message.body;
        NSLog(@"%@", message.name, jsData);
        //读取js function的字符串
        NSString *jsFunctionString = jsData[@"result"];
        //拼接调用该方法的js字符串(convertDictionaryToJson:方法将NSDictionary转成JSON格式的字符串)
        NSString *jsonString = [NSDictionary convertDictionaryToJson:@{@"test":@"123", @"data":@"666"}];
        NSString *jsCallBack = [NSString stringWithFormat:@"(%@)(%@);", jsFunctionString, jsonString];
        //执行回调
        [self.weWebView evaluateJavaScript:jsCallBack completionHandler:^(id _Nullable result, NSError * _Nullable error) {
            if (error) {
                NSLog(@"err is %@", error.domain);
            }
        }];
    }
}

注意,message的body只能是NSNumber,NSString,NSDate,NSArray,NSDictionary,NSNull这几种类型,
所以我们无法将js函数直接原生,在需要进行回调的环境下我们将js回调函数转为String后再传给原生,再由原生获取后进行回调操作,实际上这是已经进行了动态js注入。

body.png

OC调用JS

动态注入js方法

- (void)evaluateJavaScript:(NSString *)javaScriptString 
     completionHandler:(void (^ _Nullable)(_Nullable id, 
          NSError * _Nullable error))completionHandler;

示例代码

// 此处是设置需要调用的js方法以及将对应的参数传入,需要以字符串的形式
NSString *jsFounction = [NSString stringWithFormat:@"getAppConfig('%@')", APP_CHANNEL_ID];
// 调用API方法
 [self.weexWebView evaluateJavaScript:jsFounction 
    completionHandler:^(id object, NSError * _Nullable error) {
        NSLog(@"obj:%@---error:%@", object, error);
    }];

纯文本网页
UIWebView字体正常,替换成WKweb后字体很小解决办法。

let js = "var meta = document.createElement(`meta`); meta.setAttribute(`name`, `viewport`); meta.setAttribute(`content`, `width=device-width`); document.getElementsByTagName(`head`)[0].appendChild(meta)"

let wkUserScript:WKUserScript = WKUserScript.init(source: js, injectionTime .atDocumentEnd, forMainFrameOnly: true)

let wkUC:WKUserContentController = WKUserContentController.init()
wkUC.addUserScript(wkUserScript)

let wkWebConfig = WKWebViewConfiguration.init()
wkWebConfig.userContentController = wkUC

let wkPreferences:WKPreferences = WKPreferences.init()
wkPreferences.minimumFontSize = 28
wkWebConfig.preferences = wkPreferences

_wkWebView = WKWebView.init(frame: **,  configuration: wkWebConfig)

WKUIDelegate

UIWebView会由网页弹出alert, 替换成wk后不能弹出。解决办法

WKUIDelegate是webView在user interface上的代理,
共有5个可选类型的代理方法。为webView提供了原生的弹框,而不是JavaScript里的提示框。

虽然JavaScript的提示框可以做的跟原生一样,但是对于ios开发者来说,如果要更改提示框就不方便了。提供这个代理可让ios端更加灵活的修改提示框的样式。

js中有三种提示框:Alert,Confirm,prompt对应该代理如下:

/*  警告 */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    [[[UIAlertView alloc] initWithTitle:@"警告框" message:message delegate:nil cancelButtonTitle:@"确认" otherButtonTitles: nil] show];
    completionHandler();
}
///** 确认框 */
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
    [[[UIAlertView alloc] initWithTitle:@"确认框" message:message delegate:nil cancelButtonTitle:@"确认" otherButtonTitles: nil] show];

    completionHandler(1);
}
/**  输入框 */
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
    [[[UIAlertView alloc] initWithTitle:@"输入框" message:prompt delegate:nil cancelButtonTitle:@"确认" otherButtonTitles: nil] show];
 completionHandler(@"谁!");
}

除了上面的三个方法还有两个代理方法。

// 创建新的webView
// 可以指定配置对象、导航动作对象、window特性。如果没用实现这个方法,不会加载链接,如果返回的是原webview会崩溃。
- (nullable WKWebView *)webView:(WKWebView *)webView 
        createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration 
        forNavigationAction:(WKNavigationAction *)navigationAction 
        windowFeatures:(WKWindowFeatures *)windowFeatures;
// webview关闭时回调
- (void)webViewDidClose:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
- (void)webView:(WKWebView *)webView 
        decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
 WKFrameInfo *sFrame = navigationAction.sourceFrame;//navigationAction的出处
 WKFrameInfo *tFrame = navigationAction.targetFrame;//navigationAction的目标
//只有当  tFrame.mainFrame == NO;时,表明这个 WKNavigationAction 将会新开一个页面。
//才会调用- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
}

新开一个webView。
如果我们只是显示网页就感觉这么做耗性能没有必要。

- (WKWebView *)webView:(WKWebView *)webView 
        createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
  WKFrameInfo *frameInfo = navigationAction.targetFrame;
  if (![frameInfo isMainFrame]) {
      [webView loadRequest:navigationAction.request];
  }
    return nil;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351