iOS开发 WKWebView和js的交互(vue.js)

iOS开发 WKWebView和js的交互(vue.js),第1张

废话少说,直接上代码:

原生代码:

//
//  WKWebViewController.m
//
//  Created by 小韭小菜 on 2022/4/13.
//

#import "WKWebViewController.h"
#import 
#import 

// 进度条
static NSString *const kEstimatedProgressKey = @"estimatedProgress";
// js调用原生的桥
static NSString *const JavascriptBackBridge = @"goBack";
static NSString *const JavascriptShowJSDataBridge = @"showJSData";

@interface WKWebViewController ()
// webview
@property (nonatomic, strong) WKWebView *wkWebView;
// 进度条
@property (nonatomic, strong) UIProgressView *progressView;
/** 导航栏左上角页面返回按钮 */
@property (nonatomic, strong) UIBarButtonItem *closeButtonItem;
/** 导航栏左上角按钮 */
@property (nonatomic, strong) UIBarButtonItem *leftBarButtonItem;
// 桥的数组
@property (nonatomic, strong) NSArray *scriptMessageHandlerArray;
// urlString
@property (nonatomic, copy) NSString *urlString;
// 本地URL
@property (nonatomic, copy) NSString *localUrl;

@end

@implementation WKWebViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self p_initData];
    [self setupSubView];
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    // addScriptMessageHandler 很容易导致循环引用
    [self addScriptMessageHandler];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    // 因此这里要记得移除handlers
    [self removeAllScriptMessageHandler];
}

- (void)setupSubView {
    
    self.navigationItem.title = @"加载中...";
    self.view.backgroundColor = [UIColor whiteColor];
    [self p_setNavigationRightButton];
    
    if (@available(iOS 11.0, *)) {
        self.wkWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
    [self.view addSubview:self.wkWebView];
    [self.view addSubview:self.progressView];
    [self.view insertSubview:self.wkWebView belowSubview:self.progressView];
    
    [self.progressView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(self.view.mas_top).offset(0);
        make.left.right.mas_equalTo(0);
        make.height.mas_equalTo(1.5);
    }];
        
    [self.wkWebView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.bottom.right.mas_equalTo(0);
    }];
    
    // 加载url
    [self loadDocument:self.localUrl url:self.urlString];
}

- (void)p_initData {
    self.scriptMessageHandlerArray = @[JavascriptBackBridge,JavascriptShowJSDataBridge];
    // 进度条
    [self.wkWebView addObserver:self forKeyPath:kEstimatedProgressKey options:NSKeyValueObservingOptionNew context:NULL];
    [self.wkWebView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
    
    self.localUrl = @"testh5";
}

//添加js监听
- (void)addScriptMessageHandler {
    [self removeAllScriptMessageHandler];
    for (NSString *tempHanderStr in self.scriptMessageHandlerArray) {
        [self.wkWebView.configuration.userContentController addScriptMessageHandler:self name:tempHanderStr];
    }
}

//移除js监听
- (void)removeAllScriptMessageHandler {
    for (NSString *tempHanderStr in self.scriptMessageHandlerArray) {
        [self.wkWebView.configuration.userContentController removeScriptMessageHandlerForName:tempHanderStr];
    }
}

#pragma mark - 内部方法

// 设置导航栏按钮
-(void)p_setNavigationBarButton {
    
    return;
    // 下面代码暂且没有使用到,因为本地只有一个h5页面;
    if (!self.wkWebView.canGoBack) {
        // 这个地方代码,视项目具体处理情况
        self.navigationItem.leftBarButtonItem = nil;
        self.navigationItem.leftBarButtonItems = @[];
        return;
    }
    
    // 返回按钮
    if (!self.leftBarButtonItem) {
        self.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icon_back"] style:(UIBarButtonItemStylePlain) target:self action:@selector(p_backAction)];;
    }
    
    // 关闭按钮
    if (!self.closeButtonItem) {
        self.closeButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icon_close"] style:(UIBarButtonItemStylePlain) target:self action:@selector(p_closeAction)];
    }
    
    self.navigationItem.leftBarButtonItem = nil;
    self.navigationItem.leftBarButtonItems = @[];
    self.navigationItem.leftBarButtonItems = @[self.leftBarButtonItem, self.closeButtonItem];
}

// 设置导航栏右侧按钮
-(void)p_setNavigationRightButton {
    
    UIBarButtonItem *rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"调用JS" style:(UIBarButtonItemStylePlain) target:self action:@selector(p_evaluateJS)];
    self.navigationItem.rightBarButtonItem = rightButtonItem;
}

// 返回
- (void)p_backAction {
    if (self.wkWebView.canGoBack) {
        [self p_setNavigationBarButton];
        [self.wkWebView goBack];
        return;
    }
    [self p_closeAction];
    
}

// 关闭
- (void)p_closeAction {
    if(self.presentingViewController && self.navigationController.viewControllers.count == 1) {
        // 代表是模态
        [self dismissViewControllerAnimated:YES completion:nil];
    } else {
        [self.navigationController popViewControllerAnimated:YES];
    }
}

// 注入js
static NSInteger count = 0;
- (void)p_evaluateJS {
//    原生调JS,原生向JS传值 实现该方法即可: [webView evaluateJavaScript:<> completionHandler:^(id _Nullable obj, NSError * _Nullable error){}];
    NSMutableString *jsString = [[NSMutableString alloc] init];
    // 其中 nativeData() 是js的方法名字
    count ++;
    NSString *params = [NSString stringWithFormat:@"nativeData %ld",(long)count];
    // 注意,方法的参数需要是string格式,其他格式js接受可能出错;
    [jsString appendFormat:@"receiveAppData('%@')",params];
    
    [self.wkWebView evaluateJavaScript:jsString completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
        if (error) {
            NSLog(@"注入JS的error==%@",error);
        }
    }];
}


// 加载本地html或网络的url,二者取一
- (void)loadDocument:(NSString *)docName url:(NSString *)urlString {
    if (!docName && !urlString) {
        return;
    }
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSURLRequest *request;
        if (docName) {
            // 本地
            NSString *path = [[NSBundle mainBundle] bundlePath];
            NSURL *baseURL = [NSURL fileURLWithPath:path];
            NSString *htmlPath = [[NSBundle mainBundle] pathForResource:docName
                                                                  ofType:@"html"];
            NSString *htmlCont = [NSString stringWithContentsOfFile:htmlPath
                                                            encoding:NSUTF8StringEncoding
                                                               error:nil];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.wkWebView loadHTMLString:htmlCont baseURL:baseURL];
            });
        } else {
            // 网络
            // 参数需要处理下编码,防止参数里面有中文/object/嵌套url,而导致 [NSURL URLWithString:url] 为nil;
            NSString *loadUrl = [self p_bwtURLParamsEncode:urlString];
            request = [NSURLRequest requestWithURL:[NSURL URLWithString:loadUrl] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.wkWebView loadRequest:request];
            });
        }
    });
}

/// URLString只编码参数, 编码格式为UTF8
- (NSString *)p_bwtURLParamsEncode:(NSString *)url {
    // 有时候编码整个url会有问题;所以只把URLstring的参数编码
    NSString *urlString = url;
    if (urlString.length == 0) {
        return urlString;
    }
    NSArray *stringArray = [urlString componentsSeparatedByString:@"?"];
    if (stringArray.count > 1) {
        // 代表有拼接参数 需要把后面的参数 以防请求的参数里面有汉字、jsonstring对象
        NSString *onlyUrl = [stringArray firstObject];
        // 取到参数的string +1是去除?
        NSString *paramsString = [urlString substringFromIndex:onlyUrl.length + 1];
        if (![self p_isURLEncoded:paramsString]) {
            // 参数需要编码下
            urlString = [NSString stringWithFormat:@"%@?%@",onlyUrl,[paramsString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
        }
    }
    return urlString;
}

// 判断url是否编码过
- (BOOL)p_isURLEncoded:(NSString *)url {
    NSString *decodeStr = url.stringByRemovingPercentEncoding;
    return ![decodeStr isEqualToString:url];
}



#pragma mark - Delegate

// KVO回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:kEstimatedProgressKey]) {
        [self.progressView setAlpha:1.0f];
        [self.progressView setProgress:self.wkWebView.estimatedProgress animated:YES];
        if(self.wkWebView.estimatedProgress >= 1.0f) {
            [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{
                [self.progressView setAlpha:0.0f];
            } completion:^(BOOL finished) {
                [self.progressView setProgress:0.0f animated:NO];
            }];
        }
    } else if ([keyPath isEqualToString:@"title"]) {
        if (object == self.wkWebView) {
            if (self.wkWebView.title) {
                self.navigationItem.title = self.wkWebView.title;
            }
        }
    }
}

//===== WKUIDelegate Methods
/*
 以下三个代理方法全都是与界面d出提示框相关的,针对web界面的三种提示框(警告框,提示框,输入框)分别对应三种代理方法
 */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    
    NSLog(@"runJavaScriptAlertPanelWithMessage");
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示"
                                                                             message:message
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"确定"
                                                        style:UIAlertActionStyleCancel
                                                      handler:^(UIAlertAction *action) {
                                                          completionHandler();
                                                      }]];
    [self presentViewController:alertController animated:YES completion:^{}];
}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    NSLog(@"runJavaScriptConfirmPanelWithMessage");
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }]];
    [self presentViewController:alert animated:YES completion:nil];
}

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler {
    NSLog(@"runJavaScriptConfirmPanelWithMessage");
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:prompt preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.textColor = [UIColor darkTextColor];
        textField.placeholder = defaultText;
    }];
    
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler([[alert.textFields lastObject] text]);
    }]];
    
    [self presentViewController:alert animated:YES completion:nil];
}

//===== WKNavigationDelegate Methods 页面跳转的代理方法
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    
    if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
        [self p_setNavigationBarButton];
    }
    
    NSLog(@"%@",navigationAction.request.URL.absoluteString);
    NSURL *navigationURL = navigationAction.request.URL;
    if ([navigationURL.absoluteString hasPrefix:@"itms-apps://"] || [navigationURL.absoluteString containsString:@"itunes.apple.com"]) {
        // 跳转到App Store
        [[UIApplication sharedApplication] openURL:navigationURL];
        decisionHandler(WKNavigationActionPolicyCancel);
    } else if ([navigationURL.absoluteString hasPrefix:@"tel:"]) {
        // 拨打电话
        [[UIApplication sharedApplication] openURL:navigationURL];
        decisionHandler(WKNavigationActionPolicyCancel);
    } else if ([navigationURL.absoluteString hasPrefix:@"weixin:"]) {
        // 跳转到微信
        [[UIApplication sharedApplication] openURL:navigationURL];
        decisionHandler(WKNavigationActionPolicyCancel);
    } else if ([navigationURL.absoluteString hasPrefix:@"alipays:"] || [navigationURL.absoluteString hasPrefix:@"alipay:"]) {
        // 跳转到支付宝
        [[UIApplication sharedApplication] openURL:navigationURL];
        decisionHandler(WKNavigationActionPolicyCancel);
    } else if ([navigationURL.absoluteString isEqualToString:@"about:blank"]) {
        // 空白blank 情况
        decisionHandler(WKNavigationActionPolicyAllow);
    } else if ([navigationURL.absoluteString hasPrefix:@"data:"]) {
        decisionHandler(WKNavigationActionPolicyAllow);
    } else if ([navigationURL.absoluteString hasPrefix:@"file:"]) {
        decisionHandler(WKNavigationActionPolicyAllow);
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

// 接收到服务器跳转请求之后调用(重定向)
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
    [self p_setNavigationBarButton];
}

// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
    decisionHandler(WKNavigationResponsePolicyAllow);
}

// 当main frame的导航开始请求时,会调用此方法
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
}

// 当main frame导航完成时,会回调
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
//    [self p_evaluateJS];
}

// 这与用于授权验证的API,与AFN、UIWebView的授权验证API是一样的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler {
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}

// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
}

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:JavascriptBackBridge]) {
        // 返回
        dispatch_async(dispatch_get_main_queue(), ^{
            [self p_backAction];
        });
    }
    
    if ([message.name isEqualToString:JavascriptShowJSDataBridge]) {
        // 展示js传过来的内容
        NSDictionary *body = message.body;
        if (![body isKindOfClass:[NSDictionary class]]) {
            NSLog(@"传递过来的数据格式不是dic");
            return;
        }
        NSLog(@"传递过来的内容:%@", body);
        NSString *message = body[@"message"];
        message = message.length ? message : @"没内容啊";
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:message delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alert show];
        });
    }
}

#pragma mark - 懒加载方法
- (WKWebView *)wkWebView {
    if (!_wkWebView) {
        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        // 设置偏好设置
        config.preferences = [[WKPreferences alloc] init];
        // 默认为0
        config.preferences.minimumFontSize = 10;
        // 在iOS上默认为NO,表示不能自动通过窗口打开
        config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
        // web内容处理池,由于没有属性可以设置,也没有方法可以调用,不用手动创建
        config.processPool = [[WKProcessPool alloc] init];
        // 通过JS与webview内容交互
        config.userContentController = [[WKUserContentController alloc] init];
        config.selectionGranularity = WKSelectionGranularityCharacter;
        _wkWebView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
        // 水平方向禁用滚动
        _wkWebView.scrollView.showsHorizontalScrollIndicator = NO;
        _wkWebView.scrollView.showsVerticalScrollIndicator = NO;
        // 页面大小自适应,且不允许用户改动
        //_webView.scalesPageToFit = YES;
        // 清除背景色
        _wkWebView.UIDelegate = self;
        _wkWebView.navigationDelegate = self;
    }
    return _wkWebView;
}

- (UIProgressView *)progressView {
    if (!_progressView) {
        UIProgressView *progressView = [[UIProgressView alloc] initWithFrame:CGRectZero];
        [progressView setTransform: CGAffineTransformMakeScale(1.0f, 1.5f)];
        [progressView setProgressTintColor:[UIColor redColor]];
        [progressView setTrackTintColor:[UIColor clearColor]];
        _progressView = progressView;
    }
    return _progressView;
}

- (void)dealloc {
    [self.wkWebView removeObserver:self forKeyPath:kEstimatedProgressKey context:nil];
    [self.wkWebView removeObserver:self forKeyPath:@"title" context:nil];
    NSLog(@"%s",__func__);
}

@end

js代码:




    
    
    测试
    
    
    
    
    
    
    
    
    
    
    


    
        
            
                点击测试{{item}}方法
            
        
    








欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/990625.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存