废话少说,直接上代码:
原生代码:
//
// 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}}方法
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)