// // RQWebViewViewController.m // RQCommon // // Created by 张嵘 on 2018/11/27. // Copyright © 2018 张嵘. All rights reserved. // #import "RQWebViewViewController.h" #import "WKWebViewJavascriptBridge.h" #import @interface RQWebViewViewController () /// webView @property (nonatomic, weak, readwrite) WKWebView *webView; /// 进度条 @property (nonatomic, readwrite, strong) UIProgressView *progressView; /// 返回按钮 @property (nonatomic, readwrite, strong) UIBarButtonItem *backItem; /// 关闭按钮 (点击关闭按钮 退出WebView) @property (nonatomic, readwrite, strong) UIBarButtonItem *closeItem; /// viewModel @property (nonatomic, strong, readonly) RQWebViewModel *viewModel; @end @implementation RQWebViewViewController @dynamic viewModel; - (void)dealloc{ RQDealloc; /// remove observer ,otherwise will crash [_webView stopLoading]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; /// 清除缓存 NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes]; NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0]; [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{ }]; [self.navigationController.navigationBar addSubview:self.progressView]; if (self.viewModel.request) { [self.webView loadRequest:self.viewModel.request]; } else if (RQStringIsNotEmpty(self.viewModel.requestUrl)) { /// 加载请求数据 NSString *encodedString = (NSString*) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)self.viewModel.requestUrl,(CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]",NULL, kCFStringEncodingUTF8)); NSURL *httpUrl = [NSURL URLWithString:encodedString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:httpUrl]; [self.webView loadRequest:request]; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.progressView removeFromSuperview]; } - (void)viewDidLoad { [super viewDidLoad]; /// 添加断言,request错误 应用直接crash // NSParameterAssert(self.viewModel.request); [self.navigationItem setLeftBarButtonItems:@[self.backItem]]; [self.navigationItem setRightBarButtonItems:@[self.closeItem]]; ///CoderMikeHe FIXED: 切记 lightempty_ios 是前端跟H5商量的结果,请勿修改。 NSString *userAgent = @""; if (!(RQIOSVersion>=9.0)) [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"userAgent":userAgent}]; /// 注册JS WKUserContentController *userContentController = [[WKUserContentController alloc] init]; /// 这里可以注册JS的处理 涉及公司私有方法 这里笔者不作处理 if (self.viewModel.webViewType == RQWebViewType_Exam) { [userContentController addScriptMessageHandler:self name:@"backView"]; [userContentController addScriptMessageHandler:self name:@"displayRow"]; [userContentController addScriptMessageHandler:self name:@"displayCol"]; } WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; configuration.allowsInlineMediaPlayback = YES; if (@available(iOS 10.0, *)) { configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone; } NSMutableString *jsString = [NSMutableString string]; /// 自适应屏幕宽度 [jsString appendString:@"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"]; /// 禁止长按 [jsString appendString:@"document.documentElement.style.webkitTouchCallout='none';"]; /// 禁止选择 [jsString appendString:@"document.documentElement.style.webkitUserSelect='none';"]; /// 关闭H5的缩放手势 [jsString appendString:@"var script = document.createElement('meta');" "script.name = 'viewport';" "script.content=\"width=device-width, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\";" "document.getElementsByTagName('head')[0].appendChild(script);"]; WKUserScript *userScript = [[WKUserScript alloc] initWithSource:jsString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; // 添加自适应屏幕宽度js调用的方法 [userContentController addUserScript:userScript]; /// 赋值userContentController configuration.userContentController = userContentController; WKWebView *webView = [[WKWebView alloc] initWithFrame:RQ_SCREEN_BOUNDS configuration:configuration]; webView.navigationDelegate = self; webView.UIDelegate = self; if (@available(iOS 9.0, *)) { webView.customUserAgent = userAgent; } else { // Fallback on earlier versions } self.webView = webView; [self.view addSubview:webView]; /// oc调用js [webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) { NSLog(@"navigator.userAgent.result is ++++ %@", result); }]; /// 监听数据 @weakify(self); /// binding self.viewModel.avatarUrlString [RACObserve(self.webView, title) subscribeNext:^(NSString *titleStr) { @strongify(self); NSLog(@"%@",titleStr); /// CoderMikeHe FIXED: 这里只设置导航栏的title 以免self.title 设置了tabBarItem.title if (!self.viewModel.shouldDisableWebViewTitle) self.titleView.title = self.webView.title; }]; [RACObserve(self.webView, loading) subscribeNext:^(NSNumber *currentLoadingState) { @strongify(self) BOOL isLoading = [currentLoadingState boolValue]; if (isLoading) { NSLog(@"--- webView is loading ---"); if ((self.viewModel.webViewType == RQWebViewType_Exam)) { [self enterLandscapeFullScreen:UIInterfaceOrientationLandscapeLeft animated:YES]; [self.webView setNeedsLayout]; [self.webView layoutIfNeeded]; } }else { NSLog(@"--- webView isn't loading ---"); } }]; [RACObserve(self.webView, estimatedProgress) subscribeNext:^(NSNumber *estimatedProgressValue) { NSLog(@"%@",estimatedProgressValue); @strongify(self) [self.progressView setAlpha:1.0f]; [self.progressView setProgress:self.webView.estimatedProgress animated:YES]; if(self.webView.estimatedProgress >= 1.0f) { @weakify(self); [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{ @strongify(self) [self.progressView setAlpha:0.0f]; } completion:^(BOOL finished) { @strongify(self) [self.progressView setProgress:0.0f animated:NO]; }]; } }]; /// 添加刷新控件 if(self.viewModel.shouldPullDownToRefresh){ [self.webView.scrollView rq_addHeaderRefresh:^(MJRefreshNormalHeader *header) { @strongify(self) [self.webView reload]; }]; [self.webView.scrollView.mj_header beginRefreshing]; } self.webView.scrollView.contentInset = self.contentInset; /// CoderMikeHe: 适配 iPhone X + iOS 11,去掉安全区域 if (@available(iOS 11.0, *)) { RQAdjustsScrollViewInsets_Never(webView.scrollView); } } #pragma mark - 事件处理 - (void)_backItemDidClicked{ /// 返回按钮事件处理 /// 可以返回到上一个网页,就返回到上一个网页 if (self.webView.canGoBack) { [self.webView goBack]; }else{/// 不能返回上一个网页,就返回到上一个界面 /// 判断 是Push还是Present进来的, if (self.presentingViewController) { if (self.viewModel.webViewType == RQWebViewType_VIP) { [self dismissViewControllerAnimated:YES completion:NULL]; } else { [self.viewModel.services dismissViewModelAnimated:YES completion:NULL]; } } else { [self.viewModel.services popViewModelAnimated:YES]; } } } - (void)_closeItemDidClicked{ /// 判断 是Push还是Present进来的 if (self.presentingViewController) { if (self.viewModel.webViewType == RQWebViewType_VIP) { [self dismissViewControllerAnimated:YES completion:NULL]; } else { [self.viewModel.services dismissViewModelAnimated:YES completion:NULL]; } } else { [self.viewModel.services popViewModelAnimated:YES]; } } - (UIEdgeInsets)contentInset{ BOOL isExamUrl = (self.viewModel.webViewType == RQWebViewType_Exam); return UIEdgeInsetsMake(isExamUrl? 0 : (RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), isExamUrl? 0 : 0, isExamUrl? 0 : RQ_APPLICATION_SAFEAREA_BOTTOM_HEIGHT, isExamUrl? 0 : 0); } - (void)enterLandscapeFullScreen:(UIInterfaceOrientation)orientation animated:(BOOL)animated { CGFloat cellHeight = RQ_SCREEN_HEIGHT - RQ_APPLICATION_STATUS_BAR_HEIGHT - RQ_APPLICATION_SAFEAREA_BOTTOM_HEIGHT; CGFloat rotation = 0; CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration; if (orientation == UIInterfaceOrientationLandscapeLeft) { rotation = M_PI_2; }else if (orientation == UIInterfaceOrientationLandscapeRight) { rotation = M_PI_2 * 3; } UIView *presentView = self.webView; @weakify(presentView) CGRect landRect = CGRectMake(RQ_APPLICATION_STATUS_BAR_HEIGHT, 0, cellHeight, RQ_SCREEN_WIDTH); [UIView animateWithDuration:duration animations:^{ @strongify(presentView) presentView.layer.affineTransform = CGAffineTransformMakeRotation(rotation); } completion:^(BOOL finished) { @strongify(presentView) presentView.layer.bounds = landRect; }]; } #pragma mark - WKScriptMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ /// js call OC function // [QMUIConsole log:[NSString stringWithFormat:@"js call OC function :%@",message.name]]; NSLog(@"js call OC function :%@",message.name); if ([message.name isEqualToString:@"displayRow"]) { [self enterLandscapeFullScreen:UIInterfaceOrientationLandscapeLeft animated:YES]; [self.webView setNeedsLayout]; [self.webView layoutIfNeeded]; } else if ([message.name isEqualToString:@"backView"]) { [userContentController removeScriptMessageHandlerForName:@"backView"]; [userContentController removeScriptMessageHandlerForName:@"displayRow"]; [userContentController removeScriptMessageHandlerForName:@"displayCol"]; // [self _backItemDidClicked]; [self.viewModel.services popViewModelAnimated:YES]; } } #pragma mark - WKNavigationDelegate /// 内容开始返回时调用 - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation { /// 不显示关闭按钮 if(self.viewModel.shouldDisableWebViewClose) return; UIBarButtonItem *backItem = self.navigationItem.leftBarButtonItems.firstObject; if (backItem) { if ([self.webView canGoBack]) { [self.navigationItem setLeftBarButtonItems:@[backItem, self.closeItem]]; } else { [self.navigationItem setLeftBarButtonItems:@[backItem]]; } } } // 导航完成时,会回调(也就是页面载入完成了) - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation { if (self.viewModel.shouldPullDownToRefresh) [webView.scrollView.mj_header endRefreshing]; [webView evaluateJavaScript:@"document.body.scrollHeight" completionHandler:^(id _Nullable result, NSError * _Nullable error) { double height = [result doubleValue]; NSLog(@"%f",height); }]; } // 导航失败时会回调 - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error { if (self.viewModel.shouldPullDownToRefresh) [webView.scrollView.mj_header endRefreshing]; } // 在发送请求之前,决定是否跳转 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSLog(@"navigationAction.request.URL: %@", navigationAction.request.URL); decisionHandler(WKNavigationActionPolicyAllow); } - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { decisionHandler(WKNavigationResponsePolicyAllow); } - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler { NSURLCredential *cred = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential, cred); } #pragma mark - WKUIDelegate - (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { /// CoderMike Fixed : 解决点击网页的链接 不跳转的Bug。 WKFrameInfo *frameInfo = navigationAction.targetFrame; if (![frameInfo isMainFrame]) { [webView loadRequest:navigationAction.request]; } return nil; } #pragma mark runJavaScript - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler { // [NSObject rq_showAlertViewWithTitle:nil message:message confirmTitle:@"我知道了"]; completionHandler(); } - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler { completionHandler(YES); } - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler { completionHandler(defaultText); } #pragma mark - Getter & Setter - (UIProgressView *)progressView { if (!_progressView) { CGFloat progressViewW = RQ_SCREEN_WIDTH; CGFloat progressViewH = 3; CGFloat progressViewX = 0; CGFloat progressViewY = CGRectGetHeight(self.navigationController.navigationBar.frame) - progressViewH + 1; UIProgressView *progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(progressViewX, progressViewY, progressViewW, progressViewH)]; progressView.progressTintColor = RQ_MAIN_TINTCOLOR; progressView.trackTintColor = [UIColor clearColor]; [self.view addSubview:progressView]; self.progressView = progressView; } return _progressView; } - (UIBarButtonItem *)backItem { if (_backItem == nil) { _backItem = [UIBarButtonItem rq_backItemWithTitle:@"返回" imageName:@"backIcon" target:self action:@selector(_backItemDidClicked)]; } return _backItem; } - (UIBarButtonItem *)closeItem { if (!_closeItem) { _closeItem = [UIBarButtonItem rq_systemItemWithTitle:@"关闭" titleColor:RQ_MAIN_COLOR imageName:nil target:self selector:@selector(_closeItemDidClicked) textType:YES]; } return _closeItem; } @end