博客> WKWebView 实现交互调 - 调用相机回传图片
WKWebView 实现交互调 - 调用相机回传图片
2019-12-11 03:29 评论:0 阅读:1159 Cean_alloc
ios 调用相机 WKWebView交互 混编 webview和js交互

JS 调用 OC

window.webkit.messageHandlers.methodName.postMessage(value); methodName: 和协议中名字相对应, 还和html发送消息名字一样

OC 调用 JS

[webView evaluate[removed]@"fromApp()" completionHandler:nil]; fromApp(): JS 必须实现的方法

#import <UIKit>

@interface XQWKWebViewController : UIViewController
/**网页 html的全路径 */
- (instancetype)initWithH5FullPath:(NSString *)fullPath isPush:(BOOL)isPush;
/**本地 html的文件名, 注意本地html拖入工程时选引用(显示的是蓝色文件夹), 这样关联的CSS才能找到路径 */
- (instancetype)initWithH5FileName:(NSString *)fileName isPush:(BOOL)isPush;

- (void)reloadWebView;
@end
#import "XQWKWebViewController.h"
#import <WebKit>
#import "XQTabBarController.h"
#import "XQHomeViewController.h"
#import "AppDelegate.h"
#import <AVFoundation>
#import "XQWXApiManager.h"

// 协议中名字相对应,还和html发送消息名字一样
#define kNativeMethod           @"invokeNativeMethod"       //调用相机
#define kNativeLoginMethod      @"NativeLoginHandlerMethod" // 登录成功后处理
#define kReloginMethod          @"ReloginMethod"            // 重登录
#define kCallPhoneMethod        @"CallPhone"                // 打电话
#define kClossLoginMethod       @"ClossLoginMethod"         // 关闭登录界面
#define kChooseLocationMethod   @"ChooseLocationCity"       // 定位
#define kWeChatLoginMethod      @"WeChatLogin"              // 微信登录

#define kEstimatedProgressKeyPath @"estimatedProgress"

static void *estimatedChanged = &estimatedChanged;

@interface XQWKWebViewController ()<WKNavigationDelegate>
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) UIProgressView *progressView;
/** 加载路径 */
@property (nonatomic, copy) NSString *requestPath;
/** h5文件名 */
@property (nonatomic, copy) NSString *h5FileName;
/** 是否全路径 */
@property (nonatomic, assign, getter=isFullPath) BOOL fullPath;

@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) XQTipView *tipView;

/** push页面 */
@property (nonatomic, assign) BOOL isPush;
/** js方法已添加 */
@property (nonatomic) BOOL IsAddJS;

@end

@implementation XQWKWebViewController

- (BOOL)isFullPath {
    return self.requestPath ? YES : NO;
}

- (instancetype)initWithH5FileName:(NSString *)fileName isPush:(BOOL)isPush {
    if (self = [super init]) {
        self.h5FileName = fileName;
        self.isPush = isPush;
    }
    return self;
}

- (instancetype)initWithH5FullPath:(NSString *)fullPath isPush:(BOOL)isPush {
    if (self = [super init]) {
        self.requestPath = fullPath;
        self.isPush = isPush;
    }
    return self;
}

- (void)reloadWebView {
    [self.webView reload];
}

- (UIProgressView *)progressView {
    if (!_progressView) {
        _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 20, YDJRScreenWidth, 3)];
        [self.view addSubview:_progressView];
    }
    return _progressView;
}

- (XQTipView *)tipView {
    if (!_tipView) {
        _tipView = [[XQTipView alloc] initWithFrame:self.view.bounds];
    }
    return _tipView;
}

- (void)setUpWebView {
    //1.创建config对象, 设置config的属性
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.preferences.javaScriptCanOpenWindowsAutomatically = YES;//default value is NO

    //2.创建userContentController对象; 绑定config
    WKUserContentController *userContentController = [WKUserContentController new];
    config.userContentController = userContentController;
    //2.1 监听JS消息的发送, 实现JS调OC必须要做的一步, 由于循环引用的问题, 该步骤移到 viewDidAppear 中

    //3.创建webView对象; 绑定config
    CGRect frame = self.view.frame;
    frame.origin.y = 20;
    frame.size.height = frame.size.height - 20;
    self.webView = [[WKWebView alloc] initWithFrame:frame configuration:config];
    self.webView.backgroundColor = [UIColor groupTableViewBackgroundColor];
    self.webView.navigationDelegate = self;
    [self.webView addSubview:self.tipView.loadingView];
    [self.view addSubview:self.webView];
}

- (BOOL)automaticallyAdjustsScrollViewInsets {
    return NO;
}

//- (void)setCookies {
//    
//    // 这里的HOST是你web服务器的域名地址
//    NSString *host = self.requestPath;
//    // 比如你之前登录的网站地址是abc.com(当然前面要加http://,如果你服务器需要端口号也可以加上端口号),那么这里的HOST就是http://abc.com
//    
//    NSHTTPCookieStorage *myCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage];
//    for (NSHTTPCookie *cookie in [myCookie cookies]) {
//        XQLog(@"cookie====%@====", cookie);
//        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; // 保存
//    }
//    // 寻找URL为HOST的相关cookie,不用担心,步骤2已经自动为cookie设置好了相关的URL信息
//    NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:host]];
//    
//    // 设置header,通过遍历cookies来一个一个的设置header
//    for (NSHTTPCookie *cookie in cookies) {
//        
//        // cookiesWithResponseHeaderFields方法,需要为URL设置一个cookie为NSDictionary类型的header,注意NSDictionary里面的forKey需要是@"Set-Cookie"
//        NSDictionary *dic = [NSDictionary dictionaryWithObject:[[NSString alloc] initWithFormat:@"%@=%@",[cookie name],[cookie value]] forKey:@"Set-Cookie"];
//        NSArray *headeringCookie = [NSHTTPCookie cookiesWithResponseHeaderFields:dic forURL:[NSURL URLWithString:host]];
//        
//        // 通过setCookies方法,完成设置,这样只要一访问URL为HOST的网页时,会自动附带上设置好的header
//        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:headeringCookie forURL:[NSURL URLWithString:host] mainDocumentURL:nil];
//    }
//}

- (void)webViewLoadHTML {

    if (self.isFullPath) {
//        [self setCookies];
        NSString *token = kUserDefaultsGetObJectForKey(kXQUserTokenKey);
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.requestPath.xq_URL];
        XQLog(@"\n加载 %@ 时的 token:%@", self.requestPath, token);
        // 验证token, 封装到 header, iOS8 后webView不再自动缓存cookies了, 类名NSHTTPCookie
        [request setValue:kStringNotNil(token) forHTTPHeaderField:kXQUserTokenKey];
        [self.webView loadRequest:request.mutableCopy];
        return;
    }

    NSString *mainBundlePath = [[NSBundle mainBundle] bundlePath];
    //html : 是放所有 xxx.html 文件 的文件夹名
    NSString *basePath = [NSString stringWithFormat:@"%@/html", mainBundlePath];
    NSURL *url = [NSURL fileURLWithPath:basePath isDirectory:YES];

    NSString *htmlPath = [basePath stringByAppendingPathComponent:self.h5FileName];
    NSString *htmlString = [NSString stringWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
    [self.webView loadHTMLString:htmlString baseURL:url];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = kGlobalColor;

    [self setUpWebView];

    //监听estimatedProgress属性的变化
    [self.webView addObserver:self forKeyPath:kEstimatedProgressKeyPath options:NSKeyValueObservingOptionNew context:estimatedChanged];

    __weak typeof(self) weakSelf = self;
    [self.webView.scrollView addHeaderRefresh:^{
        [weakSelf.webView.scrollView endHeaderRefresh];
        [weakSelf.webView reload];
    }];
    [self webViewLoadHTML];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.tabBarController.tabBar.hidden = _isPush;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    if (!_IsAddJS) {
        [self addAllScriptMessageHandler];
    }
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    if (_IsAddJS) {
        [self removeAllScriptMessageHandler];
    }
}

- (void)addAllScriptMessageHandler {
    // 注意:name参数需要和协议中名字相对应 还和html发送消息名字一样
    WKUserContentController *userCC = self.webView.configuration.userContentController;
    [userCC addScriptMessageHandler:self name:kNativeMethod];
    [userCC addScriptMessageHandler:self name:kNativeLoginMethod];
    [userCC addScriptMessageHandler:self name:kReloginMethod];
    [userCC addScriptMessageHandler:self name:kCallPhoneMethod];
    [userCC addScriptMessageHandler:self name:kClossLoginMethod];
    [userCC addScriptMessageHandler:self name:kChooseLocationMethod];
    [userCC addScriptMessageHandler:self name:kWeChatLoginMethod];
    _IsAddJS = YES;
}

- (void)removeAllScriptMessageHandler {
    // 循环引用, 必须移除, 添加和移除一一对应
    WKUserContentController *userCC = self.webView.configuration.userContentController;
    [userCC removeScriptMessageHandlerForName:kNativeMethod];
    [userCC removeScriptMessageHandlerForName:kNativeLoginMethod];
    [userCC removeScriptMessageHandlerForName:kReloginMethod];
    [userCC removeScriptMessageHandlerForName:kCallPhoneMethod];
    [userCC removeScriptMessageHandlerForName:kClossLoginMethod];
    [userCC removeScriptMessageHandlerForName:kChooseLocationMethod];
    [userCC removeScriptMessageHandlerForName:kWeChatLoginMethod];
    _IsAddJS = NO;
}

#pragma mark - WKScriptMessageHandler Delegate

// 接收到JS发送消息时调用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    // 1.判定消息的名字
    // 2.是, 获取JS发送过来的参数(message.body), 调用OC代码

    if ([message.name isEqualToString:kChooseLocationMethod]) {
        [self locationHandler:message.body];
    } else if ([message.name isEqualToString:kNativeLoginMethod]) {
        XQLog(@"\n登录后获取数据:%@", message.body);
        [self finishLoginHandler:message.body];
    } else if ([message.name isEqualToString:kReloginMethod]) {
        [self relogin];
    } else if ([message.name isEqualToString:kCallPhoneMethod]) {
        [self callPhone:message.body];
    } else if ([message.name isEqualToString:kClossLoginMethod]) {
        [self clossedLogin];
    } else if ([message.name isEqualToString:kNativeMethod]) {
        XQLog(@"js 调用相机");
        [self openImagePicker];
    } else if ([message.name isEqualToString:kWeChatLoginMethod]) {
        XQLog(@"js 微信登录");
        [self wechatLogin];
    }
}

- (void)locationHandler:(id)body {
    NSData *jsonData = [body dataUsingEncoding:NSUTF8StringEncoding];
    NSError *err;
    NSDictionary *dic = [NSJSONSerialization JSONObjectWith[removed] base64Str];
        [self.webView evaluate[removed]jsStr completionHandler:nil];
    }
}

- (void)hiddenTabbar:(NSString *)urlString {
    // url=mine , _history, 贷款记录和我的页面
    NSInteger length = urlString.length;
    NSString *subString = [urlString substringFromIndex:length-8];
    XQLog(@"subString: %@", subString);
    self.tabBarController.tabBar.hidden = !([subString isEqualToString:@"url=mine"] || [subString isEqualToString:@"_history"]);
}

#pragma mark - WKNavigation Delegate

//发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    //允许
    decisionHandler(WKNavigationActionPolicyAllow);
    //    decisionHandler(WKNavigationActionPolicyCancel);

#ifdef DEBUG
//    [self.tipView.unConnectView removeFromSuperview];
    XQLog(@"%s", __func__);
// ([navigationAction.request.URL.host.lowercaseString isEqualToString:@"www.baidu.com"])
    NSString *token = kUserDefaultsGetObJectForKey(kXQUserTokenKey);
    XQLog(@"\n本地最新 token:%@", token);
    NSDictionary *dic = [navigationAction.request allHTTPHeaderFields];
    XQLog(@"\n跳转 h5-url%@ 的 token:%@", navigationAction.request.URL, dic);
#endif
}

//页面开始加载
//- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
//    NSLog(@"%s", __func__);
//}

//接收到请求,决定是否加载
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
    XQLog(@"%s", __func__);
// 可根据URL来判断是否加载
//   if ([navigationResponse.response.URL.host.lowercaseString isEqualToString:@"www.baidu.com"]) 
 //    decisionHandler(WKNavigationActionPolicyCancel); //不允许

//    允许加载
    decisionHandler(WKNavigationResponsePolicyAllow);
    [self hiddenTabbar:navigationResponse.response.URL.absoluteString];
}

// 接收到服务器跳转请求之后调用
//- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
//    XQLog(@"%s", __func__);
//}

//当页面内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
    XQLog(@"%s", __func__);
    // 移除h5自带的tabbar
    [webView evaluate[removed]@"document.getElementById(\"remove\").remove()" completionHandler:nil];
//    [self.tipView.unConnectView removeFromSuperview];
    [self.tipView.loadingView removeFromSuperview];
}

//页面全部加载完毕
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    XQLog(@"%s", __func__);
    // 通知 h5 是 app登录
    [self.tipView.loadingView removeFromSuperview];
    [webView evaluate[removed]@"fromApp()" completionHandler:nil];
    [webView evaluate[removed]@"fromIOSApp()" completionHandler:nil];
    [webView evaluate[removed]@"document.getElementById(\"remove\").remove()" completionHandler:nil];
    NSString *clientId = kAppDelegate.clientId;
    NSString *methodStr = [NSString stringWithFormat:@"sendClientId(\"%@\")", clientId];
    [webView evaluate[removed]methodStr completionHandler:nil];
}

//开始加载时失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    XQLog(@"%s:%@", __func__, error.localizedDescription);
    [self.tipView.loadingView removeFromSuperview];
    if (error.code == -1009) {
        [XQProgressHUD showImage:[UIImage imageNamed:@"unConnect"] status:@"网络异常"];
    } else {
        if (error.code != -999) {
        [XQProgressHUD showImage:[UIImage imageNamed:@"unConnect"] status:error.localizedDescription];
        }
    }
}

//页面返回时加载失败
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    [self.tipView.loadingView removeFromSuperview];
//    [webView addSubview:self.tipView.nothingView];
    XQLog(@"%s:%@", __func__, error.localizedDescription);
}

#pragma mark - ObserveKeyPath

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString> *)change context:(void *)context {
    if ([keyPath isEqualToString:kEstimatedProgressKeyPath]) {

        if (object == self.webView) {
            [self.progressView setProgress:self.webView.estimatedProgress animated:YES];

            if (self.webView.estimatedProgress >= 1.0) {
                [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{
                    //如果加载完毕,给progressView设置透明度
                    [self.progressView setAlpha:0.0];
                } completion:^(BOOL finished) {
                    //加载完后设置进度为0,下次重新加载进度从0开始
                    [self.progressView setProgress:0.0 animated:YES];
                }];
            }
        }
    }
}

- (void)dealloc {
    //移除监听者
    [_webView removeObserver:self forKeyPath:kEstimatedProgressKeyPath];
    XQLog(@"%s", __func__);
}

@end

时间紧, 就不整理单独的Demo了, 很多都是交互的方法, 仔细看很明了, 欢迎大神指正, 谢谢!

注意:

WKUserContentController 这个类一般会在使用 WKWebView 的时候配套使用,如果你发现项目中调用了addScriptMessageHandler 方法,就要注意了,检查有没有在VC释放前对称调用removeScriptMessageHandlerForName 方法,如果没有则引起引用循环。

dealloc 中调用无效, 因为这时候 webView 已经销毁了, 可以在 viewDidDisappear 调用方法如下:

[self.wkWebView.configuration.userContentControllerremoveScriptMessageHandlerForName:@"methodName"]; 注意 WKUserContentControllerWKWebView 中还有一个 WKWebViewConfiguration

收藏
1
sina weixin mail 回到顶部