Files
xj-ios/XinjiangProject/XinjiangProject/Project/Base/XJBaseWebViewVC.m
T
2026-03-05 18:07:03 +08:00

302 lines
11 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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 是强引用。
// 如果直接传 selfself → 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