|
@@ -0,0 +1,268 @@
|
|
|
+//
|
|
|
+// HomeStatisticsView.m
|
|
|
+// KulexiuForTeacher
|
|
|
+//
|
|
|
+// Created by 王智 on 2024/12/26.
|
|
|
+//
|
|
|
+
|
|
|
+#import "HomeStatisticsView.h"
|
|
|
+#import <WebKit/WebKit.h>
|
|
|
+#import <WeakWebViewScriptMessageDelegate.h>
|
|
|
+#import "KSBaseWKWebViewController.h"
|
|
|
+#import "KSWebLoadRefreshView.h"
|
|
|
+#import <AuthChallengeManager.h>
|
|
|
+
|
|
|
+@interface HomeStatisticsView ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler,UIScrollViewDelegate>
|
|
|
+
|
|
|
+@property (nonatomic, strong) WKWebView *myWebView;
|
|
|
+
|
|
|
+@property (nonatomic, strong) NSString *url;
|
|
|
+
|
|
|
+@property (nonatomic, strong) KSWebLoadRefreshView *errorView;
|
|
|
+
|
|
|
+@property (nonatomic, copy) RefreshViewHeightCallback callback;
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation HomeStatisticsView
|
|
|
+
|
|
|
++ (instancetype)sharedInstance {
|
|
|
+ HomeStatisticsView *view = [[[NSBundle mainBundle] loadNibNamed:@"HomeStatisticsView" owner:nil options:nil] firstObject];
|
|
|
+ return view;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)configWebViewRefreshCallback:(RefreshViewHeightCallback)callback {
|
|
|
+ if (callback) {
|
|
|
+ self.callback = callback;
|
|
|
+ }
|
|
|
+ self.url = [NSString stringWithFormat:@"%@%@",WEBHOST,@"/#/home-echarts"];
|
|
|
+ [self addSubview:self.myWebView];
|
|
|
+ [self.myWebView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.mas_equalTo(self);
|
|
|
+ make.top.mas_equalTo(self.mas_top);
|
|
|
+ make.bottom.mas_equalTo(self.mas_bottom);
|
|
|
+ }];
|
|
|
+ [self loadRequest];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)configUserAgent:(WKWebViewConfiguration *)config {
|
|
|
+ NSString *oldUserAgent = config.applicationNameForUserAgent;
|
|
|
+ NSString *newAgent = [NSString stringWithFormat:@"%@ %@ %@",oldUserAgent,AGENT_NAME,AGENT_DOMAIN];
|
|
|
+ config.applicationNameForUserAgent = newAgent;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
++ (WKProcessPool*)singleWkProcessPool {
|
|
|
+ static WKProcessPool *sharedPool;
|
|
|
+ static dispatch_once_t onceToken;
|
|
|
+ dispatch_once(&onceToken, ^{
|
|
|
+ sharedPool = [[WKProcessPool alloc] init];
|
|
|
+ });
|
|
|
+ return sharedPool;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)loadRequest {
|
|
|
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0f];
|
|
|
+ [self.myWebView loadRequest:request];
|
|
|
+}
|
|
|
+
|
|
|
+- (NSDictionary *)convertJsonStringToNSDictionary:(NSString *)jsonString {
|
|
|
+ if (jsonString == nil) {
|
|
|
+ return nil;
|
|
|
+ }
|
|
|
+ NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
+ NSError *error;
|
|
|
+ NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
|
|
|
+ if (error) {
|
|
|
+ NSLog(@"jsonString解析失败:%@", error);
|
|
|
+ return nil;
|
|
|
+ }
|
|
|
+ return json;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - WKScriptMessageHandler
|
|
|
+- (void)userContentController:(WKUserContentController *)userContentController
|
|
|
+ didReceiveScriptMessage:(WKScriptMessage *)message {
|
|
|
+
|
|
|
+ if ([message.name isEqualToString:SCRIPT_NAME]) {
|
|
|
+ NSDictionary *parm = [self convertJsonStringToNSDictionary:message.body];
|
|
|
+ // 回到主线程
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ [self handleScriptMessageSource:parm];
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)handleScriptMessageSource:(NSDictionary *)parm {
|
|
|
+ if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"openWebView"]) {
|
|
|
+ NSDictionary *valueDic = [parm ks_dictionaryValueForKey:@"content"];
|
|
|
+
|
|
|
+ KSBaseWKWebViewController *detailCtrl = [[KSBaseWKWebViewController alloc] init];
|
|
|
+ detailCtrl.url = [valueDic ks_stringValueForKey:@"url"];
|
|
|
+ detailCtrl.parmDic = valueDic;
|
|
|
+ NSInteger orientation = [valueDic ks_integerValueForKey:@"orientation"];
|
|
|
+ BOOL isLandScape = orientation == 0 ? YES : NO;
|
|
|
+ detailCtrl.ks_landScape = isLandScape;
|
|
|
+ [self postMessage:parm];
|
|
|
+
|
|
|
+ [self.naviController pushViewController:detailCtrl animated:YES];
|
|
|
+ }
|
|
|
+// else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@""]) { // 下载文件
|
|
|
+//
|
|
|
+// }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark ----- lazying
|
|
|
+- (WKWebView *)myWebView {
|
|
|
+ if (!_myWebView) {
|
|
|
+ WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
|
|
|
+ config.selectionGranularity = WKSelectionGranularityDynamic;
|
|
|
+ config.allowsInlineMediaPlayback = YES;
|
|
|
+ config.mediaTypesRequiringUserActionForPlayback = NO;
|
|
|
+ config.processPool = [HomeStatisticsView singleWkProcessPool];
|
|
|
+ config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
|
|
|
+ [self configUserAgent:config];
|
|
|
+
|
|
|
+ //自定义的WKScriptMessageHandler 是为了解决内存不释放的问题
|
|
|
+ WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
|
|
|
+ //这个类主要用来做native与JavaScript的交互管理
|
|
|
+ WKUserContentController * wkUController = [[WKUserContentController alloc] init];
|
|
|
+ [wkUController addScriptMessageHandler:weakScriptMessageDelegate name:SCRIPT_NAME];
|
|
|
+ config.userContentController = wkUController;
|
|
|
+
|
|
|
+ WKPreferences *preferences = [WKPreferences new];
|
|
|
+ // 是否支出javaScript
|
|
|
+ preferences.javaScriptEnabled = YES;
|
|
|
+ //不通过用户交互,是否可以打开窗口
|
|
|
+ preferences.javaScriptCanOpenWindowsAutomatically = YES;
|
|
|
+ config.preferences = preferences;
|
|
|
+ _myWebView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
|
|
|
+ _myWebView.UIDelegate = self;
|
|
|
+ _myWebView.navigationDelegate = self;
|
|
|
+ _myWebView.scrollView.bounces = NO;
|
|
|
+ _myWebView.scrollView.opaque = NO;
|
|
|
+ _myWebView.scrollView.alwaysBounceVertical = NO;
|
|
|
+ _myWebView.scrollView.alwaysBounceHorizontal = NO;
|
|
|
+ _myWebView.scrollView.showsVerticalScrollIndicator = NO;
|
|
|
+ _myWebView.scrollView.showsHorizontalScrollIndicator = NO;
|
|
|
+ _myWebView.scrollView.delegate = self;
|
|
|
+ }
|
|
|
+ return _myWebView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)postMessage:(NSDictionary *)parm {
|
|
|
+ if (_myWebView) {
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ NSString *jsString = [parm mj_JSONString];
|
|
|
+ [self.myWebView evaluateJavaScript:[NSString stringWithFormat:@"postMessage(%@,'*')", jsString] completionHandler:nil];
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+- (void)dealloc {
|
|
|
+ [[_myWebView configuration].userContentController removeScriptMessageHandlerForName:SCRIPT_NAME];
|
|
|
+ [_myWebView loadHTMLString:@"" baseURL:nil];
|
|
|
+ [_myWebView removeFromSuperview];
|
|
|
+ _myWebView = nil;
|
|
|
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
|
|
+ [self showErrorView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler
|
|
|
+{
|
|
|
+ dispatch_queue_t queue = dispatch_queue_create("webViewChallengeQueue", NULL);
|
|
|
+ dispatch_async(queue, ^{
|
|
|
+ if (SSL_AUTH) {
|
|
|
+
|
|
|
+ NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
|
|
|
+ NSURLCredential *customCredential = nil;
|
|
|
+
|
|
|
+ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
|
|
|
+ // 默认信任
|
|
|
+ customCredential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
|
+ disposition = NSURLSessionAuthChallengeUseCredential;
|
|
|
+ }
|
|
|
+ else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
|
|
|
+ // client authentication
|
|
|
+ SecIdentityRef identity = NULL;
|
|
|
+ SecTrustRef trust = NULL;
|
|
|
+ if ([AuthChallengeManager extractIdentity:&identity andTrust:&trust filePath:CERT_PATH]) {
|
|
|
+ SecCertificateRef certificate = NULL;
|
|
|
+ SecIdentityCopyCertificate(identity, &certificate);
|
|
|
+ const void*certs[] = {certificate};
|
|
|
+ CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
|
|
|
+ customCredential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
|
|
|
+ disposition = NSURLSessionAuthChallengeUseCredential;
|
|
|
+ // 释放 CFArrayRef 和 SecCertificateRef
|
|
|
+ CFRelease(certArray);
|
|
|
+ CFRelease(certificate);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (completionHandler) {
|
|
|
+ completionHandler(disposition, customCredential);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
|
|
|
+ if (challenge.previousFailureCount == 0) {
|
|
|
+ NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
|
+ completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
|
|
+ } else {
|
|
|
+ completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+- (KSWebLoadRefreshView *)errorView {
|
|
|
+ if (!_errorView) {
|
|
|
+ _errorView = [KSWebLoadRefreshView shareInstance];
|
|
|
+ _errorView.hideBackButton = YES;
|
|
|
+ MJWeakSelf;
|
|
|
+ [_errorView failViewActionCallback:^(BOOL isBack) {
|
|
|
+ [weakSelf hideErrorView];
|
|
|
+ if (isBack) {
|
|
|
+
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [weakSelf loadRequest];
|
|
|
+ }
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ return _errorView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showErrorView {
|
|
|
+ if (![self.subviews containsObject:self.errorView]) {
|
|
|
+ [self addSubview:self.errorView];
|
|
|
+ [self.errorView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.top.bottom.mas_equalTo(self);
|
|
|
+ }];
|
|
|
+ [self bringSubviewToFront:self.errorView];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)hideErrorView {
|
|
|
+ if ([self.subviews containsObject:self.errorView]) {
|
|
|
+ [self.errorView removeFromSuperview];
|
|
|
+ }
|
|
|
+}
|
|
|
+/*
|
|
|
+// Only override drawRect: if you perform custom drawing.
|
|
|
+// An empty implementation adversely affects performance during animation.
|
|
|
+- (void)drawRect:(CGRect)rect {
|
|
|
+ // Drawing code
|
|
|
+}
|
|
|
+*/
|
|
|
+
|
|
|
+@end
|