// // KBWebViewViewController.m // keyBoard // // Created by 张伟 on 2025/11/10. // #import "KBWebViewViewController.h" #import #import "KBConfig.h" #import "Masonry.h" @interface KBWebViewViewController () @property(nonatomic, strong) WKWebView * webView; // 🟢 顶部加载进度条 @property(nonatomic, strong) UIProgressView *progressView; @property(nonatomic, assign) BOOL observingProgress; @end @implementation KBWebViewViewController + (instancetype)legalViewControllerWithType:(KBLegalDocumentType)type { KBWebViewViewController *vc = [[KBWebViewViewController alloc] init]; NSString *remoteURL = [self kb_remoteURLForLegalDocumentType:type]; if (remoteURL.length > 0) { vc.url = remoteURL; } else { // vc.htmlString = [self kb_htmlForLegalDocumentType:type]; } return vc; } + (void)presentLegalDocumentType:(KBLegalDocumentType)type fromViewController:(UIViewController *)viewController { if (![viewController isKindOfClass:UIViewController.class]) { return; } KBWebViewViewController *vc = [self legalViewControllerWithType:type]; UINavigationController *nav = viewController.navigationController; if (nav) { [nav pushViewController:vc animated:YES]; return; } [viewController presentViewController:vc animated:YES completion:nil]; } + (nullable NSNumber *)legalDocumentTypeNumberFromQueryValue:(NSString *)queryValue { NSString *value = queryValue.lowercaseString ?: @""; if ([value isEqualToString:@"privacy"]) { return @(KBLegalDocumentTypePrivacyPolicy); } if ([value isEqualToString:@"membership"]) { return @(KBLegalDocumentTypeMembershipAgreement); } if ([value isEqualToString:@"terms"]) { return @(KBLegalDocumentTypeTermsOfService); } return nil; } + (NSString *)queryValueForLegalDocumentType:(KBLegalDocumentType)type { switch (type) { case KBLegalDocumentTypePrivacyPolicy: return @"privacy"; case KBLegalDocumentTypeMembershipAgreement: return @"membership"; case KBLegalDocumentTypeTermsOfService: default: return @"terms"; } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = UIColor.whiteColor; [self configUI]; self.kb_titleLabel.hidden = true; } - (void)configUI { // 1. 配置 JS 交互 WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; WKUserContentController *userContentController = [[WKUserContentController alloc] init]; [userContentController addScriptMessageHandler:self name:@"keyBoard"]; config.userContentController = userContentController; // 2. 创建 webView self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config]; self.webView.navigationDelegate = self; self.webView.backgroundColor = UIColor.clearColor; [self.view addSubview:self.webView]; [self.webView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.kb_navView.mas_bottom); make.left.right.bottom.equalTo(self.view); }]; // 🟢 3. 顶部 2 像素进度条 self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; self.progressView.trackTintColor = [UIColor clearColor]; // 背景透明 self.progressView.progressTintColor = [UIColor greenColor]; self.progressView.progress = 0.0f; self.progressView.hidden = YES; // 初始隐藏 [self.view addSubview:self.progressView]; // 约束:贴在最上面,高度 2 像素,左右撑满 [self.progressView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.kb_navView.mas_bottom); make.left.right.equalTo(self.view); make.height.mas_equalTo(2.0); }]; // 🟢 4. 监听 WKWebView 加载进度(estimatedProgress) [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; self.observingProgress = YES; // 5. 加载内容 if (self.htmlString.length > 0) { [self.webView loadHTMLString:self.htmlString baseURL:nil]; return; } NSURL *URL = [NSURL URLWithString:self.url ?: @""]; if (!URL) { [self.webView loadHTMLString:[self.class kb_fallbackErrorHTML] baseURL:nil]; return; } NSURLRequest * req = [NSURLRequest requestWithURL:URL]; [self.webView loadRequest:req]; [self.view bringSubviewToFront:self.kb_navView]; [self.view bringSubviewToFront:self.progressView]; } #pragma mark - KVO: 监听加载进度(estimatedProgress) 🟢 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == self.webView && [keyPath isEqualToString:@"estimatedProgress"]) { CGFloat progress = self.webView.estimatedProgress; // 0 ~ 1之间的进度 self.progressView.hidden = NO; [self.progressView setProgress:progress animated:YES]; if (progress >= 1.0f) { // 加载完成后延迟一点点再隐藏,顺滑一点 [UIView animateWithDuration:0.25 delay:0.25 options:UIViewAnimationOptionCurveEaseOut animations:^{ self.progressView.alpha = 0.0; } completion:^(BOOL finished) { self.progressView.progress = 0.0; self.progressView.hidden = YES; self.progressView.alpha = 1.0; }]; } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } #pragma mark - WKNavigationDelegate // 开始加载 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { NSLog(@"开始加载url"); // 开始加载时,确保进度条出现 self.progressView.hidden = NO; self.progressView.progress = 0.0; } // 加载完成 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { NSLog(@"页面加载成功"); if (webView.title.length > 0) { self.title = webView.title; } } // 加载失败 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { NSLog(@"webView load error: %@", error); // 失败时也把进度条收一下 self.progressView.hidden = YES; self.progressView.progress = 0.0; } #pragma mark - WKScriptMessageHandler (JS 调原生) - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if (![message.body isKindOfClass:NSDictionary.class]) { NSLog(@"Body参数不合法"); return; } [self handleJSMethodWithParams:message.body]; } - (void)handleJSMethodWithParams:(NSDictionary *)params { NSDictionary * body = [params copy]; NSLog(@"收到 JS 消息: %@", body); NSString * type = body[@"type"]; // 测试打印 if ([type isEqual:@"ONE_METHOD"]) { NSLog(@"come on baby js 调用你的方法了"); } // 修改title if ([type isEqual:@"changeTiele"]) { NSLog(@"%@", body); NSString * newTiele = body[@"payload"][@"data"]; self.title = newTiele; } } #pragma mark - Legal Content + (NSString *)kb_titleForLegalDocumentType:(KBLegalDocumentType)type { switch (type) { case KBLegalDocumentTypePrivacyPolicy: return KBLocalized(@"Privacy Policy"); case KBLegalDocumentTypeMembershipAgreement: return KBLocalized(@"Membership Agreement"); case KBLegalDocumentTypeTermsOfService: default: return KBLocalized(@"Agreement"); } } + (NSString *)kb_remoteURLForLegalDocumentType:(KBLegalDocumentType)type { switch (type) { case KBLegalDocumentTypePrivacyPolicy: return KB_PRIVACY_POLICY_URL; case KBLegalDocumentTypeMembershipAgreement: return KB_MEMBERSHIP_AGREEMENT_URL; case KBLegalDocumentTypeTermsOfService: default: return KB_TERMS_OF_SERVICE_URL; } } + (NSString *)kb_fallbackErrorHTML { return @"

Page unavailable

The requested document could not be loaded.

"; } #pragma mark - Clean up 🟢 - (void)dealloc { if (self.observingProgress) { @try { [self.webView removeObserver:self forKeyPath:@"estimatedProgress"]; } @catch (NSException *exception) { NSLog(@"removeObserver estimatedProgress exception: %@", exception); } } // 顺便把 JS 通道也移除,避免潜在的循环引用 [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"keyBoard"]; } @end