美文网首页
WKWebView使用、进度条、与原生交互

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

作者: CoderCurtis | 来源:发表于2017-11-29 17:31 被阅读151次

    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

    相关文章

      网友评论

          本文标题:WKWebView使用、进度条、与原生交互

          本文链接:https://www.haomeiwen.com/subject/wmkpbxtx.html