2025-11-10 16:09:47 +08:00
//
// KBWebViewViewController . m
// keyBoard
//
// Created by 张 伟 on 2025 / 11 / 10.
//
# import "KBWebViewViewController.h"
# import < WebKit / WebKit . h >
2026-03-08 21:29:10 +08:00
# import "KBConfig.h"
# import "Masonry.h"
2025-11-10 16:09:47 +08:00
@ interface KBWebViewViewController ( ) < WKNavigationDelegate , WKScriptMessageHandler >
@ property ( nonatomic , strong ) WKWebView * webView ;
// 🟢 顶 部 加 载 进 度 条
@ property ( nonatomic , strong ) UIProgressView * progressView ;
@ property ( nonatomic , assign ) BOOL observingProgress ;
@ end
@ implementation KBWebViewViewController
2026-03-08 21:29:10 +08:00
+ ( instancetype ) legalViewControllerWithType : ( KBLegalDocumentType ) type {
KBWebViewViewController * vc = [ [ KBWebViewViewController alloc ] init ] ;
NSString * remoteURL = [ self kb_remoteURLForLegalDocumentType : type ] ;
if ( remoteURL . length > 0 ) {
vc . url = remoteURL ;
} else {
2026-03-09 17:34:08 +08:00
// vc . htmlString = [ self kb_htmlForLegalDocumentType : type ] ;
2026-03-08 21:29:10 +08:00
}
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 ] ;
}
2025-11-10 16:09:47 +08:00
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
2026-03-08 21:29:10 +08:00
self . view . backgroundColor = UIColor . whiteColor ;
2025-11-10 16:09:47 +08:00
[ self configUI ] ;
2026-03-10 11:25:10 +08:00
self . kb_titleLabel . hidden = true ;
2025-11-10 16:09:47 +08:00
}
- ( 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 ] ;
2026-03-08 21:29:10 +08:00
[ self . webView mas_makeConstraints : ^ ( MASConstraintMaker * make ) {
make . top . equalTo ( self . kb_navView . mas_bottom ) ;
make . left . right . bottom . equalTo ( self . view ) ;
} ] ;
2025-11-10 16:09:47 +08:00
// 🟢 3. 顶 部 2 像 素 进 度 条
self . progressView = [ [ UIProgressView alloc ] initWithProgressViewStyle : UIProgressViewStyleDefault ] ;
self . progressView . trackTintColor = [ UIColor clearColor ] ; // 背 景 透 明
self . progressView . progressTintColor = [ UIColor greenColor ] ;
self . progressView . progress = 0.0 f ;
self . progressView . hidden = YES ; // 初 始 隐 藏
[ self . view addSubview : self . progressView ] ;
// 约 束 : 贴 在 最 上 面 , 高 度 2 像 素 , 左 右 撑 满
2026-03-08 21:29:10 +08:00
[ 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 ) ;
} ] ;
2025-11-10 16:09:47 +08:00
// 🟢 4. 监 听 WKWebView 加 载 进 度 ( estimatedProgress )
[ self . webView addObserver : self
forKeyPath : @ "estimatedProgress"
options : NSKeyValueObservingOptionNew
context : nil ] ;
self . observingProgress = YES ;
2026-03-08 21:29:10 +08:00
// 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 ] ;
2025-11-10 16:09:47 +08:00
[ self . webView loadRequest : req ] ;
2026-03-08 21:29:10 +08:00
[ self . view bringSubviewToFront : self . kb_navView ] ;
[ self . view bringSubviewToFront : self . progressView ] ;
2025-11-10 16:09:47 +08:00
}
# pragma mark - KVO : 监 听 加 载 进 度 ( estimatedProgress ) 🟢
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath
ofObject : ( id ) object
change : ( NSDictionary < NSKeyValueChangeKey , id > * ) 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.0 f ) {
// 加 载 完 成 后 延 迟 一 点 点 再 隐 藏 , 顺 滑 一 点
[ 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 ( @ "页面加载成功" ) ;
2026-03-10 11:25:10 +08:00
if ( webView . title . length > 0 ) {
2026-03-08 21:29:10 +08:00
self . title = webView . title ;
}
2025-11-10 16:09:47 +08:00
}
// 加 载 失 败
- ( 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 ;
}
}
2026-03-08 21:29:10 +08:00
# 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 @ "<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><style>body{font-family:-apple-system,BlinkMacSystemFont,'Helvetica Neue',sans-serif;padding:32px;color:#1f2937;}h1{font-size:22px;margin:0 0 12px;}p{font-size:15px;line-height:1.6;color:#4b5563;}</style></head><body><h1>Page unavailable</h1><p>The requested document could not be loaded.</p></body></html>" ;
}
2025-11-10 16:09:47 +08:00
# 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