WKWebView使用、进度条、与原生交互

iOS8之前加载网页都是使用<UIKit/UIKit.h>中的UIWebView,iOS8新出了一个加载网页的控件[WKWebView],它属于<WebKit/WebKit.h>,使用WKWebView相比于UIWebView益处多多,这个具体可以自行百度。从去年开始很多应用都已从iOS8开始适配,如今更不用担心这个问题了。

其他的废话不需要多说

    1. 首先,Demo中新建一个控制器作为其他控制器的父类,为了方便我公开一个返回上一控制器的方法。

BaseViewController:

#import <UIKit/UIKit.h>
#import "Masonry/Masonry.h"

@interface BaseViewController : UIViewController

- (void)zjsLeftBarButtonItemAction;

@end

#import "BaseViewController.h"

@interface BaseViewController ()

@end

@implementation BaseViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self zjsLeftBarButtonItem];
}

#pragma mark -左侧返回按钮
- (void)zjsLeftBarButtonItem
{
    if (self.navigationController.viewControllers.count > 1) {
        UIButton *backButton= [UIButton buttonWithType:UIButtonTypeCustom];
        backButton.frame = CGRectMake(0, 0, 64, 44);
        [backButton setImage:[UIImage imageNamed:@"navLeft"] forState:UIControlStateNormal];
        [backButton setImage:[UIImage imageNamed:@"navLeft"] forState:UIControlStateHighlighted];
        backButton.imageEdgeInsets = UIEdgeInsetsMake(0, -10, 0, 50);
        [backButton addTarget:self action:@selector(zjsLeftBarButtonItemAction) forControlEvents:UIControlEventTouchUpInside];
        self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    }
}

- (void)zjsLeftBarButtonItemAction
{
    [self.navigationController popViewControllerAnimated:YES];
}

重写返回按钮,并且公开了返回按钮响应事件 -> 这里对加载网页的控制器有作用,我的处理方式是如此的

- (void)zjsLeftBarButtonItemAction
{
    [self.navigationController popViewControllerAnimated:YES];
}
    1. 新建一个导航控制器 作为基类导航控制器,然后添加上右滑返回功能,实现了UINavigationControllerDelegate代理。当导航控制器的子类控制器数量为1,即当前界面为导航最外层时,右滑手势不应响应,这里避免了一个当用户在最外层右滑时,再push会产生的一个bug。
#import "BaseNavigationController.h"

@interface BaseNavigationController ()<UIGestureRecognizerDelegate, UINavigationControllerDelegate>

@end

@implementation BaseNavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.navigationBar.barTintColor = [UIColor orangeColor];
    self.navigationBar.translucent = NO;
    
    [self popGesture];
}

#pragma mark -右滑返回手势
- (void)popGesture
{
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.interactivePopGestureRecognizer.enabled  = YES;
        self.interactivePopGestureRecognizer.delegate = self;
        self.delegate = self;
    }
}

//重写系统push方法
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (self.viewControllers.count >= 1) {
        viewController.hidesBottomBarWhenPushed = YES;
    }
    
    [super pushViewController:viewController animated:animated];
    
    ///add by hgc 2018年03月06日 解决IPhoneX 模拟器下 push tabBar向上跳动
    CGRect frame = self.tabBarController.tabBar.frame;
    frame.origin.y = [UIScreen mainScreen].bounds.size.height - frame.size.height;
    self.tabBarController.tabBar.frame = frame;
}

#pragma mark -UINavigationControllerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer == self.interactivePopGestureRecognizer)
    {
        if (self.viewControllers.count == 1)
        {
            return NO;
        }
    }
    return YES;
}

- (void)dealloc
{
    NSLog(@"--dealloc: %@", NSStringFromClass([self class]));
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.interactivePopGestureRecognizer.delegate = nil;
        self.delegate = nil;
    }
}
    1. 使用WKWebView,需要引入<WebKit/WebKit.h>

WKWebView比较常用的两个代理<WKUIDelegate, WKNavigationDelegate>

采用懒加载方式创建WKWebView和进度条对象:


#import "WebViewController.h"
#import <WebKit/WebKit.h>

#define Screen_Bounds [UIScreen mainScreen].bounds

@interface WebViewController ()<WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler>

@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) UIProgressView *progressView;

@end

@implementation WebViewController

#pragma mark -lazy load
- (WKWebView *)webView
{
    if (!_webView) {
        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
        WKUserContentController *userContentController = [[WKUserContentController alloc] init];
        
        /*! @abstract Adds a script message handler.
         @param scriptMessageHandler The message handler to add.
         @param name The name of the message handler.
         @discussion Adding a scriptMessageHandler adds a function
         window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
         frames.
         */
        [userContentController addScriptMessageHandler:self name:@"yourRegisterHandle"];
        config.userContentController = userContentController;
        
        _webView = [[WKWebView alloc] initWithFrame:Screen_Bounds configuration:config];
        _webView.backgroundColor = [UIColor whiteColor];
        _webView.UIDelegate = self;
        _webView.navigationDelegate = self;
        _webView.allowsBackForwardNavigationGestures = YES;//允许右滑手势返回上一页面(网页)
    }
    return _webView;
}

- (UIProgressView *)progressView
{
    if (!_progressView) {
        _progressView = [[UIProgressView alloc] init];
        _progressView.backgroundColor = [UIColor clearColor];//背景色-若trackTintColor为clearColor,则显示背景颜色
        _progressView.progressTintColor = [UIColor blueColor];//进度条颜色
        _progressView.trackTintColor = [UIColor clearColor];//进度条还未到达的线条颜色
        
        //默认值
        _progressView.progress = 0.3;
    }
    return _progressView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.title = @"WKWebView";
    
    [self testWKWebView];
    
}

#pragma mark -父类方法
- (void)zjsLeftBarButtonItemAction
{
    NSLog(@"--父类返回按钮响应事件");
    if ([self.webView canGoBack]) {
        [self canGoBackWebViewPage];
        [self rewriteLeftBarButton:YES];
    } else {
        [self popToLastViewController];
    }
}

#pragma mark -跳转网页
- (void)canGoBackWebViewPage
{
    [self.webView goBack];
}

#pragma mark -重写左侧返回按钮 是否添加关闭按钮
- (void)rewriteLeftBarButton:(BOOL)add
{
    UIButton *backButton= [UIButton buttonWithType:UIButtonTypeCustom];
    backButton.frame = CGRectMake(0, 0, 64, 44);
    [backButton setImage:[UIImage imageNamed:@"navLeft"] forState:UIControlStateNormal];
    [backButton setImage:[UIImage imageNamed:@"navLeft"] forState:UIControlStateHighlighted];
    backButton.imageEdgeInsets = UIEdgeInsetsMake(0, -10, 0, 50);
    [backButton addTarget:self action:@selector(zjsLeftBarButtonItemAction) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    if (add) {
        UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
        closeButton.frame = CGRectMake(0, 0, 44, 44);
        closeButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
        [closeButton setTitle:@"关闭" forState:UIControlStateNormal];
//        [closeButton setImage:[UIImage imageNamed:@"close"] forState:UIControlStateNormal];
        [closeButton addTarget:self action:@selector(popToLastViewController) forControlEvents:UIControlEventTouchUpInside];
        UIBarButtonItem *closeItem = [[UIBarButtonItem alloc]initWithCustomView:closeButton];
        UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
        //正: 向右 负: 向左
        space.width = -10;
        
        NSArray *array = [NSArray arrayWithObjects:backItem, space, closeItem, nil];
        self.navigationItem.leftBarButtonItems = array;
    } else {
        self.navigationItem.leftBarButtonItem = backItem;
    }
}

#pragma mark -pop
- (void)popToLastViewController
{
    [self.navigationController popViewControllerAnimated:YES];
}

#pragma mark -WKWebView
- (void)testWKWebView
{
    [self.view addSubview:self.webView];
    
    NSString *url = @"https://www.baidu.com";
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    [self.webView loadRequest:request];
    
    [self.view addSubview:self.progressView];
    [self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.leading.and.trailing.equalTo(@0);
        make.top.equalTo(self.mas_topLayoutGuideBottom);
        make.height.equalTo(@2);
    }];
    
    ///KVO-title
    [self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
    
    ///KVO-estimatedProgress
    [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:NULL];
}

/**
 KVO获取网页title
 
 @param keyPath 路径
 @param object 对象
 @param change 改变
 @param context 上下文
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"title"]) {
        if ([object isEqual:self.webView]) {
            if (self.navigationController) {
                self.title = self.webView.title;
            }
        }
    } else if ([keyPath isEqualToString:@"estimatedProgress"]) {
        if ([object isEqual:self.webView]) {
            NSLog(@"-change: %@ ---estimatedProgress: %f", change, self.webView.estimatedProgress);
            if ([change[@"new"] floatValue] < 0.3) {
                [self.progressView setProgress:0.3];
            } else {
                [self.progressView setProgress:self.webView.estimatedProgress animated:YES];
            }
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

#pragma mark -WKNavigationDelegate
///发送请求之前 决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSLog(@"--发送请求之前 决定是否跳转-decidePolicyForNavigationAction");
    NSLog(@"%@",navigationAction.request.URL.absoluteString);
    
    //允许跳转
    decisionHandler(WKNavigationActionPolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationActionPolicyCancel);
}

///页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
    self.progressView.hidden = NO;
    NSLog(@"--页面开始加载调用-didStartProvisionalNavigation");
}

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
    NSLog(@"--接收到响应后,决定是否跳转--decidePolicyForNavigationResponse");
    NSLog(@"%@",navigationResponse.response.URL.absoluteString);
    
    //允许跳转
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationResponsePolicyCancel);
}

- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
    NSLog(@"--当内容开始返回时调用-didCommitNavigation");
    
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    NSLog(@"--页面加载完成之后调用-didFinishNavigation");
    self.progressView.hidden = YES;
}

- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation
{
    NSLog(@"--接收到服务器跳转请求之后-didReceiveServerRedirectForProvisionalNavigation");
    
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    NSLog(@"--页面加载失败调用-didFailProvisionalNavigation");
    self.progressView.hidden = YES;
}

#pragma mark -WKScriptMessageHandler 监控webView按钮点击事件
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    NSLog(@"==%@", message.body);
    NSString *functionName = message.body;
    if ([functionName isEqualToString:@"methodA"]) {
        
        //To do...
        
    } else if ([functionName isEqualToString:@"methodB"]) {
        //To do...
        
    }
}

#pragma mark -WKUIDelegate
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    NSLog(@"--创建一个新的webView-createWebViewWithConfiguration");
    return [[WKWebView alloc] init];
}

// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler
{
    NSLog(@"%@---%@",prompt, defaultText);
    completionHandler(@"http");
    
}
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
{
    NSLog(@"%@",message);
    completionHandler(YES);
    
}
// 警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
    NSLog(@"%@",message);
    completionHandler();
}

- (void)dealloc
{
    //添加了什么观察者 就一定要记着remove它  否则会造成崩溃
    [self.webView removeObserver:self forKeyPath:@"title"];
    [self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
}

对于获取网页标题和网页加载进度,采用的KVO方式监听到值的改变然后修改相应UI。 为什么采用KVO方式监听,这个IDE中有说明:

比如标题:

鼠标左键点到WKWebView,按住command + 鼠标左键会弹出如下图内容,然后左键点击Jump to Definition找到title 有如下说明.


屏幕快照 2017-11-29 下午5.22.22.png
/*! @abstract The page title.
 @discussion @link WKWebView @/link is key-value observing (KVO) compliant
 for this property.
 */
@property (nullable, nonatomic, readonly, copy) NSString *title;

加载进度等变化采用KVO方式IDE中也有明确提到.

一定要记得在dealloc中移除监听,否则返回销毁控制器时会导致程序崩溃。

这里进度条默认给了0.3的初始值,这样的话即使开始加载网页也能给用户一个视觉效果。

以前公司维护的一个超级老的应用里面有大量的原生与JS等互调情况,不过使用的是UIWebView和<JavaScriptCore/JavaScriptCore.h>,关于这些网上可以搜到很多相关文章介绍。

拿到h5按钮的点击事件对于WKWebView是很容易的:

  1. 添加代理协议WKScriptMessageHandler
  2. 为WKWebView添加配置
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:self name:@"yourRegisterHandle"];
config.userContentController = userContentController;

//初始化WKWebView时 给上config
_webView = [[WKWebView alloc] initWithFrame:Screen_Bounds configuration:config];

点击进入addScriptMessageHandler方法,你会发现该方法有如下说明:

! @abstract Adds a script message handler.
         @param scriptMessageHandler The message handler to add.
         @param name The name of the message handler.
         @discussion Adding a scriptMessageHandler adds a function
         window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
         frames.

简单的说就是,添加一个scriptMessageHander 方法, 然后window.webkit.messageHandlers.<name>.postMessage(<messageBody>)这句是需要H5方法中调用的,然后你实现WKScriptMessageHandler的代理方法:- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message,说不清楚,举例:

接上

通过addScriptMessageHandler,添加了yourRegisterHandle方法,即[userContentController addScriptMessageHandler:self name:@"yourRegisterHandle"];

然后H5方法中需要写上这么一句window.webkit.messageHandlers.<name>.postMessage(<messageBody>),这里的<messageBody>是消息体,可以定义一个字典为参数,也可以只是字符串,两端定义统一即可。

最后你在WKScriptMessageHandler的代理方法- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message中根据<messageBody>来实现原生需要做的事情。
比如: 

NSString *functionName = message.body;
if ([functionName isEqualToString:@"methodA"]) {
        
        //To do...
        
} else if ([functionName isEqualToString:@"methodB"]) {
        //To do...
        
}

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,975评论 3 119
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,350评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 我家里兄弟姐妹多,但是很团结,小的矛盾冲突早已经忘记了,留在脑海里的就是,没有爸爸了,大哥养家很辛苦,我们要...
    大漠孤烟丁阅读 289评论 0 0
  • 不想说话的男人第一章听课感情这种事既没有证明,也没有定律来左右它的结果。所以可以肯定是神左右了它的走向和归宿,也所...
    阿枭阅读 380评论 0 0