|
@@ -0,0 +1,366 @@
|
|
|
+//
|
|
|
+// RQWebViewViewController.m
|
|
|
+// RQCommon
|
|
|
+//
|
|
|
+// Created by 张嵘 on 2018/11/27.
|
|
|
+// Copyright © 2018 张嵘. All rights reserved.
|
|
|
+//
|
|
|
+
|
|
|
+#import "RQWebViewViewController.h"
|
|
|
+#import "WKWebViewJavascriptBridge.h"
|
|
|
+#import <AlipaySDK/AlipaySDK.h>
|
|
|
+
|
|
|
+@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
|