302 lines
11 KiB
Objective-C
302 lines
11 KiB
Objective-C
//
|
||
// XJBaseWebViewVC.m
|
||
// ScarHousekeeper
|
||
//
|
||
// Created by 张小万 on 2021/1/21.
|
||
//
|
||
|
||
#import "XJBaseWebViewVC.h"
|
||
|
||
@interface XJBaseWebViewVC ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate>
|
||
|
||
// 以下三个属性声明后未使用,暂时保留
|
||
//@property (nonatomic, strong)NSURL *originalUrl;
|
||
//@property (nonatomic, copy)NSString *videoUrl;
|
||
//@property (nonatomic, copy)NSString *messageType;
|
||
|
||
|
||
@end
|
||
|
||
// ── 弱引用代理 ──────────────────────────────────────────────────────────────
|
||
// WKUserContentController 对 addScriptMessageHandler 的 handler 是强引用。
|
||
// 如果直接传 self,self → wkWebView → configuration → userContentController → self,
|
||
// 形成循环引用,VC 永远无法释放。
|
||
// 解决方案:用一个弱引用中间对象转发消息。
|
||
@interface XJWeakScriptMessageHandler : NSObject <WKScriptMessageHandler>
|
||
@property (nonatomic, weak) id<WKScriptMessageHandler> delegate;
|
||
+ (instancetype)handlerWithDelegate:(id<WKScriptMessageHandler>)delegate;
|
||
@end
|
||
|
||
@implementation XJWeakScriptMessageHandler
|
||
+ (instancetype)handlerWithDelegate:(id<WKScriptMessageHandler>)delegate {
|
||
XJWeakScriptMessageHandler *handler = [[XJWeakScriptMessageHandler alloc] init];
|
||
handler.delegate = delegate;
|
||
return handler;
|
||
}
|
||
- (void)userContentController:(WKUserContentController *)userContentController
|
||
didReceiveScriptMessage:(WKScriptMessage *)message {
|
||
[self.delegate userContentController:userContentController didReceiveScriptMessage:message];
|
||
}
|
||
@end
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
|
||
@implementation XJBaseWebViewVC
|
||
|
||
|
||
#pragma mark - LifeCycle
|
||
- (void)viewDidLoad {
|
||
[super viewDidLoad];
|
||
self.view.backgroundColor = [UIColor whiteColor];
|
||
self.isHidenNaviBar = false;
|
||
[self addNavigationItemWithImageNames:@[@"whiteBack_icon"] isLeft:YES target:self action:@selector(backVC) tags:@[@""]];
|
||
[self addSubviews];
|
||
if (self.webTitle) {
|
||
self.navigationItem.title = self.webTitle;
|
||
}
|
||
}
|
||
|
||
- (void)backVC {
|
||
|
||
[self.navigationController popViewControllerAnimated:YES];
|
||
}
|
||
|
||
- (void)dealloc {
|
||
// 移除所有 scriptMessageHandler,配合 XJWeakScriptMessageHandler 彻底避免内存泄漏
|
||
WKUserContentController *ucc = self.wkWebView.configuration.userContentController;
|
||
[ucc removeScriptMessageHandlerForName:@"JSCallback"];
|
||
[ucc removeScriptMessageHandlerForName:@"htmlBack"];
|
||
[ucc removeScriptMessageHandlerForName:@"finishActivity"];
|
||
}
|
||
|
||
- (void)viewWillAppear:(BOOL)animated{
|
||
[super viewWillAppear:animated];
|
||
if (_wkWebView) {
|
||
_wkWebView.navigationDelegate = self;
|
||
_wkWebView.UIDelegate = self;
|
||
_wkWebView.allowsBackForwardNavigationGestures = true;
|
||
|
||
}
|
||
}
|
||
|
||
|
||
- (void)viewWillDisappear:(BOOL)animated{
|
||
[super viewWillDisappear:animated];
|
||
_wkWebView.navigationDelegate = nil;
|
||
_wkWebView.UIDelegate = nil;
|
||
// ⚠️ 以下代码会清除整个 App 的所有 Cookie,影响其他模块登录状态,已注释
|
||
// NSHTTPCookie *cookie;
|
||
// NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
|
||
// for (cookie in [storage cookies]) {
|
||
// [storage deleteCookie:cookie];
|
||
// }
|
||
// [[NSURLCache sharedURLCache] removeAllCachedResponses];
|
||
// [self clearWebViewCache];
|
||
}
|
||
|
||
#pragma mark - Create UI
|
||
- (void)addSubviews{
|
||
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
|
||
WKPreferences *preferences = [[WKPreferences alloc]init];
|
||
// preferences.javaScriptEnabled = YES;
|
||
preferences.javaScriptCanOpenWindowsAutomatically = YES;
|
||
preferences.minimumFontSize = 10;
|
||
configuration.preferences = preferences;
|
||
|
||
// ── 统一使用 configuration 自带的 userContentController ──────────────────
|
||
// Bug 修复:原先在下方又新建了 wkUController 并覆盖 configuration.userContentController,
|
||
// 导致此处注册的 JSCallback / htmlBack 被丢弃,永远收不到 JS 消息。
|
||
// Bug 修复:改用 XJWeakScriptMessageHandler 代理,防止循环引用内存泄漏。
|
||
XJWeakScriptMessageHandler *weakHandler = [XJWeakScriptMessageHandler handlerWithDelegate:self];
|
||
[configuration.userContentController addScriptMessageHandler:weakHandler name:@"JSCallback"];
|
||
[configuration.userContentController addScriptMessageHandler:weakHandler name:@"htmlBack"];
|
||
[configuration.userContentController addScriptMessageHandler:weakHandler name:@"finishActivity"]; // 不带参数的调用
|
||
|
||
// 原写法(已废弃):直接传 self 会造成循环引用
|
||
// [configuration.userContentController addScriptMessageHandler:self name:@"JSCallback"];
|
||
// [configuration.userContentController addScriptMessageHandler:self name:@"htmlBack"];
|
||
// ────────────────────────────────────────────────────────────────────────
|
||
|
||
//防止搜索框自动放大 注入js代码
|
||
// 原代码中 userController 声明后从未使用,已移除
|
||
// WKUserContentController *userController = [WKUserContentController new];
|
||
NSString *jScript = @"var script = document.createElement('meta');script.name = 'viewport';script.content='width=device-width,user-scalable=no';document.getElementsByTagName('head')[0].appendChild(script);";
|
||
|
||
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
|
||
|
||
// 原写法(已废弃):新建 wkUController 并覆盖 configuration.userContentController,
|
||
// 导致上方注册的 JSCallback / htmlBack 全部失效
|
||
// WKUserContentController * wkUController = [[WKUserContentController alloc] init];
|
||
// [wkUController addScriptMessageHandler: self name: @"finishActivity"];
|
||
// [wkUController addUserScript:wkUScript];
|
||
// configuration.userContentController = wkUController;
|
||
|
||
[configuration.userContentController addUserScript:wkUScript];
|
||
|
||
self.wkWebView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, MyViewHEIGHT) configuration:configuration];
|
||
self.wkWebView.navigationDelegate = self;
|
||
self.wkWebView.UIDelegate = self;
|
||
if (@available(iOS 11.0, *)) {
|
||
self.wkWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||
}
|
||
[self.view addSubview:self.wkWebView];
|
||
self.wkWebView.scrollView.bounces = false;//禁止滑动
|
||
[self webViewRequest];
|
||
/*
|
||
[self.wkWebView evaluateJavaScript:@"htmlBack" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
|
||
NSLog(@"response: %@ error: %@", response, error);
|
||
if (response) {
|
||
[self back];
|
||
}
|
||
}];
|
||
*/
|
||
|
||
|
||
}
|
||
|
||
- (void)finishActivity {
|
||
|
||
|
||
}
|
||
|
||
|
||
- (void)webViewRequest{
|
||
NSString *urlString = self.webUrl;
|
||
[self hiddenAllMBProgressHUD];
|
||
if (urlString.length > 0) {
|
||
[self showMBProgressHUDOnlyWithString:@"加载中"];
|
||
NSURL *url = [NSURL URLWithString:urlString];
|
||
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
|
||
if (self.wkWebView) {
|
||
[self.wkWebView loadRequest:urlRequest];
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
#pragma mark - WKNavigationDelegate
|
||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
|
||
if ([message.name isEqualToString:@"JSCallback"]) {
|
||
if (message.body) {
|
||
NSData *jsonData = [message.body dataUsingEncoding:NSUTF8StringEncoding];
|
||
NSError *err;
|
||
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
|
||
options:NSJSONReadingMutableContainers
|
||
error:&err];
|
||
if(err) {
|
||
}
|
||
WkJSMessageType messageType = [[dic objectForKey:@"messageType"] intValue];
|
||
switch (messageType) {
|
||
case WkJSMessageTypeBack:{
|
||
// [self.navigationController popViewControllerAnimated:YES];
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
} else if ([message.name isEqualToString:@"htmlBack"]) {
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:@"fromWebToReloadData" object:nil];
|
||
}
|
||
}
|
||
|
||
#pragma mark - WKScriptMessageHandler
|
||
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
|
||
[self hiddenAllMBProgressHUD];
|
||
if (!navigationAction.targetFrame.isMainFrame) {
|
||
[webView loadRequest:navigationAction.request];
|
||
}
|
||
return nil;
|
||
}
|
||
|
||
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
|
||
if (!self.webTitle) {
|
||
self.title = @"加载中...";
|
||
}
|
||
}
|
||
|
||
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
|
||
[self hiddenAllMBProgressHUD];
|
||
|
||
if (!self.webTitle) {
|
||
self.title = webView.title;
|
||
}
|
||
|
||
}
|
||
|
||
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(nonnull NSError *)error{
|
||
[self hiddenAllMBProgressHUD];
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
#pragma mark - ACTION
|
||
- (void)ToClickNavRight:(UIButton *)button{
|
||
|
||
}
|
||
|
||
|
||
#pragma mark - 自定义
|
||
- (void)onBackClicked{
|
||
if (self.wkWebView.canGoBack) {
|
||
[self.wkWebView goBack];
|
||
} else {
|
||
// 原写法:只清缓存没有 pop,页面会卡住
|
||
// [self clearWebViewCache];
|
||
[self clearWebViewCache];
|
||
[self.navigationController popViewControllerAnimated:YES];
|
||
}
|
||
}
|
||
|
||
- (void)clearWebViewCache{
|
||
if (iOS9Later) {
|
||
NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
|
||
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
|
||
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
|
||
DLog(@"-----clear webView cache success------");
|
||
}];
|
||
} else {
|
||
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||
NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
|
||
NSError *errors;
|
||
[[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
|
||
if (!errors) {
|
||
DLog(@"-----clear wkWebView cache success------");
|
||
}
|
||
}
|
||
}
|
||
- (NSString *)URLEncodedString:(NSString *)urlString{
|
||
NSString *encodePath;
|
||
|
||
encodePath = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
|
||
return encodePath;
|
||
}
|
||
|
||
|
||
#pragma mark - SETTER
|
||
// setRightType: 与自动合成的 setter 完全相同,无需 override,保留注释备查
|
||
//- (void)setRightType:(RightNavType)rightType{
|
||
// _rightType = rightType;
|
||
//}
|
||
|
||
- (void)setWebUrl:(NSString *)webUrl{
|
||
_webUrl = webUrl;
|
||
[self webViewRequest];
|
||
}
|
||
|
||
|
||
@end
|