JHHK

欢迎来到我的个人网站
行者常至 为者常成

WKWebView(1)

目录

介绍

一、什么是WKWebView

WKWebView 是在iOS中用于加载和显示Web内容的一个组件,它提供了一种在你的应用程序中集成Web浏览器功能的方式。理解 WKWebView 可以类比为一个简化的浏览器是合理的,但需要注意一些细微的区别。

1. WKWebView 作为一个简化的浏览器:
加载和显示Web内容:
WKWebView 负责加载并显示Web页面,支持HTML、CSS和JavaScript等Web技术。

导航:
通过 WKNavigationDelegate 协议,你可以控制Web导航的行为,拦截请求,处理重定向,等等。

JavaScript交互:
通过 WKWebView 的方法,你可以与JavaScript进行交互,从JavaScript调用Objective-C/Swift代码,以及在Objective-C/Swift中调用JavaScript代码。

Cookie和缓存:
WKWebView 管理着Web页面的Cookie和缓存,可以通过相关的方法来进行操作。

2. 区别:
没有地址栏:
WKWebView 不具备类似标准浏览器的地址栏和导航控制按钮。你需要自行实现用户界面元素来控制 WKWebView 的导航和操作。

没有多标签页:
WKWebView 实例通常表示一个单独的Web视图,而不是多个标签页。如果需要实现多标签页的效果,你可能需要管理多个 WKWebView 实例。

定制化:
与标准浏览器相比,WKWebView 提供了更多的定制和控制能力,使得你可以根据应用程序的需要进行更精细的操作。

总体而言,WKWebView可以被视为一个轻量级的、嵌入式的Web浏览器组件,用于在iOS应用程序中集成Web内容。虽然它不像标准浏览器那样具有完整的用户界面,但却提供了足够的功能来满足许多应用程序的Web集成需求。

二、调试

解决方法

// 在WKWebView中设置以下代码解决
if (@available(iOS 16.4, *)) {
    [_webView setInspectable:YES];
}

常规使用

一、发送请求

1、初始化

- (WKWebView *)wkWebView {
    if (!_wkWebView) {
        // 1、创建frame
        CGRect frame = CGRectMake(20, 200, self.view.frame.size.width - 40, self.view.frame.size.height - 300);
        
        
        // 2、创建config
        WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init];
        [config.userContentController addScriptMessageHandler:(id<WKScriptMessageHandler>)[XYProxy proxyWithTarget:self] name:@"getMessage"];
        [config.userContentController addScriptMessageHandler:(id<WKScriptMessageHandler>)[XYProxy proxyWithTarget:self] name:@"ocLog"];
        
        // js注入,注入一个alert方法,页面加载完毕弹出一个对话框。
        // WKUserScriptInjectionTimeAtDocumentStart:页面刚渲染前执行,
        // WKUserScriptInjectionTimeAtDocumentEND:页面渲染后执行。
        // forMainFrameOnly:NO(全局窗口),yes(只限主窗口)
        NSString *javaScriptSource = @"alert(\"WKUserScript注入js\");";
        WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javaScriptSource
                                                          injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
                                                       forMainFrameOnly:YES];
        // [config.userContentController addUserScript:userScript];

        
        
        // 3、创建wkwebview实例
        _wkWebView = [[WKWebView alloc] initWithFrame:frame configuration:config];
        // 两个关键协议
        _wkWebView.navigationDelegate = self;
        _wkWebView.UIDelegate = self;
        //开启safari调试
        if (@available(iOS 16.4, *)) { [_wkWebView setInspectable:YES];}
        
        // 常规设置
        _wkWebView.backgroundColor = [UIColor grayColor];
        _wkWebView.scrollView.backgroundColor = [UIColor clearColor];
        //是否允许左右划手势导航,默认不允许
        _wkWebView.allowsBackForwardNavigationGestures = YES;
    }
    return _wkWebView;
}

2、发送请求

- (void)sendRequest{
    
//    NSString * url = [[NSBundle xy_bundleName:@"XYTestModule"] pathForResource:@"test.html" ofType:nil];
    NSString * url = @"http://www.baidu.com";

    
    [self.view addSubview:self.wkWebView];
    
    
    // 清除webview的缓存
    [self __cleanWKWebViewCache];

   
    // 加载进度条设置
    self.progresslayer = [self __observeProgressForWkWebView:self.wkWebView];
    
    
    // 加载url
    if (url) {
        if ([url hasPrefix:@"http"]) { //加载网络文件
            NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
            [self.wkWebView loadRequest:req];
            
        } else {//加载本地文件
            NSURL * fileUrl = [NSURL fileURLWithPath:url];
            /**
             包含您授予系统读取权限的web内容的文件或目录的URL。
             此URL必须是基于文件的URL,并且不能为空。
             为了防止WebKit读取任何其他内容,请指定与URL参数相同的值。
             要读取与内容文件相关的其他文件,请指定一个目录。
             */
            [self.wkWebView loadFileURL:fileUrl allowingReadAccessToURL:fileUrl];
        }
    }
}

二、前进后退事件

if ([self.wkWebView canGoBack]) {
    [self.wkWebView goBack];
}

if ([self.wkWebView canGoForward]) {
    [self.wkWebView goForward];
}

三、监听事件

1、监听事件汇总

- (void)addObserverForKeyPath {
    
    /**
     title: 网页的标题,一般为 html 中的 中的内容
     URL: 网页的URL地址,为最终加载的地址
     loading: 网页是否处于加载中,YES 加载中、 NO 加载完成
     estimatedProgress: 网页加载进度
     hasOnlySecureContent: 网页上的所有资源是否已通过 https 加载
     serverTrust: 加载 HTTPS 请求服务端所信任的证书
     
     */
    // NSKeyValueObservingOptionNew 更改后的值
    // NSKeyValueObservingOptionOld 更改前的值
    // NSKeyValueObservingOptionInitial 观察初始化的值(在注册观察服务时会调用一次触发方法)
    // NSKeyValueObservingOptionPrior 分别在值修改前后触发方法(即一次修改有两次触发)
    [self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
    [self.webView addObserver:self forKeyPath:@"URL" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
    [self.webView addObserver:self forKeyPath:@"loading" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
    [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
    [self.webView addObserver:self forKeyPath:@"hasOnlySecureContent" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
    [self.webView addObserver:self forKeyPath:@"serverTrust" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
}

2、通过监听展示加载进度条

self.progresslayer = [self __observeProgressForWkWebView:self.wkWebView];

-(CALayer*)__observeProgressForWkWebView:(WKWebView*)weView{
    [weView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
    UIView *progress = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 3)];
    progress.backgroundColor = [UIColor clearColor];
    [weView addSubview:progress];
    
    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(0, 0, 0, 3);
    UIColor *tintColor = [UIColor blueColor];
    layer.backgroundColor = tintColor.CGColor;
    [progress.layer addSublayer:layer];
    return layer;
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        self.progresslayer.opacity = 1;
        //不要让进度条倒着走...有时候goback会出现这种情况
        if ([change[@"new"] floatValue] < [change[@"old"] floatValue]) { return;}
        
        self.progresslayer.frame = CGRectMake(0, 0, self.view.bounds.size.width * [change[@"new"] floatValue], 3);
        if ([change[@"new"] floatValue] == 1) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                self.progresslayer.opacity = 0;
            });
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                self.progresslayer.frame = CGRectMake(0, 0, 0, 3);
            });
        }
    }else{
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

-(void)dealloc{
    [self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress"];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

4、修改请求头的UserAgent

修改成功之后再进行url的加载,所以需要再block内做 [self.wkWebView loadRequest:req];
可以在这个代理方法内webView:decidePolicyForNavigationAction:decisionHandler:查看是否修改成功

-(void)changeUserAgentWithBlock:(void(^)(void))block{
    /**
     * 控制访问权限
     * 为了防止用户分享造成的安全隐患(分享的问题下一个版本会改),目前,我们建议H5页面对User-Agent进行判断:
     * 1. 苹果手机:提取iPhone关键字,判断是否有shoudan关键字,没有则拒绝访问。
     * 2. 安卓手机:提取Android关键字,判断是存包含微信(MicroMessenger)、QQ浏览器(MQQBrowser)、UC浏览器、新浪等主流平台的信息,包含则拒绝访问。
     * 3. 不包含iPhone和Android关键字的,全部拒绝访问。
     */
    
    // wkwebview获取userAgent修改userAgent
    // 注意:这是异步操作
    // 创建一个 WKWebView 实例,使用 CGRectZero 进行初始化,可以在稍后设置实际的 frame
    WKWebView *wkWebView = self.wkWebView;
    
    // 使用 WKWebView 的 evaluateJavaScript 方法执行 JavaScript 代码
    // navigator 是 JavaScript 中的一个对象,其中的 userAgent 属性返回当前浏览器的用户代理字符串。
    [wkWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
        // 在 JavaScript 执行完成后,处理返回的结果或错误
        
        // 将 JavaScript 返回的结果强制转换为字符串
        NSString *userAgent = result;
        
        // 在原有的 UserAgent 后面添加自定义的标识 "/shoudan"
        NSString *newUserAgent = [userAgent stringByAppendingString:@"/shoudan"];
        
        // 创建一个包含新 UserAgent 的字典
        NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newUserAgent, @"UserAgent", nil];

        // 使用 NSUserDefaults 注册默认值,这里是为了在整个应用程序中共享这个 UserAgent
        [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];

        // 立即同步 UserDefaults,确保新的 UserAgent 立即生效
        [[NSUserDefaults standardUserDefaults] synchronize];
        
        // 将新的 UserAgent 设置给 WKWebView
        // 通过调用 setCustomUserAgent: 方法,应用程序可以将自定义的用户代理字符串设置给 WKWebView 对象,使得后续该 WebView 发送的 HTTP 请求中都会携带这个自定义的用户代理信息。
        // 这样,服务器在处理请求时可以根据这个自定义信息来区分并提供特定的服务或内容。
        [wkWebView setCustomUserAgent:newUserAgent];
        
        if (block) {
            block();
        }
    }];
}

两个协议

1、WKNavigationDelegate
主要处理一些跳转、加载处理操作

  用户触发加载 (点击链接 / loadRequest / JS 跳转)
  │
  ▼
  ┌──────────────────────────────┐
  │ decidePolicyForNavigationAction │
  │  (是否允许这次跳转?)          │
  └──────────────────────────────┘
              │
              ├── decisionHandler(.cancel) → 取消加载
              │
              └── decisionHandler(.allow) → 允许加载
  │
  ▼
  ┌──────────────────────────────-┐
  │ didStartProvisionalNavigation │
  │ (开始加载请求,进入 provisional 状态)│
  └──────────────────────────────┘
  │
  ▼
  ┌──────────────────────────────────┐
  │ didReceiveServerRedirectForProvisionalNavigation │
  │   (如果有 301/302 重定向会调用)                 │
  └──────────────────────────────────┘
  │
  ▼
  ┌─────────────────────────────────────────────┐
  │ decidePolicyForNavigationResponse           │
  │   收到服务器响应 Header,是否继续处理?)        │
  └─────────────────────────────────────────────┘
              │
              ├── decisionHandler(.cancel) → 拦截响应(比如文件下载)
              │
              └── decisionHandler(.allow) → 继续加载
  │
  ▼
  ┌──────────────────────────────┐
  │ didCommitNavigation          │
  │  (内容开始返回并提交到 WebView)│
  └──────────────────────────────┘
  │
  ▼
  ┌──────────────────────────────┐
  │ didFinishNavigation          │
  │   (页面加载完成)              │
  └──────────────────────────────┘
  │
  └──► [可选] didFailProvisionalNavigation / didFailNavigation
  (加载失败时会走这里)


// 是否允许这次跳转?
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    
    NSLog(@"----在发送请求之前,决定是否跳转:%@",webView.URL);

    if (navigationAction.targetFrame.isMainFrame) {
        NSLog(@"👉 主框架加载: %@", navigationAction.request.URL.absoluteString);
    } else {
        NSLog(@"👉 iframe 加载: %@", navigationAction.request.URL.absoluteString);
    }
    
    NSURL* url = webView.URL;
    NSLog(@"url=%@",url);
    if ([url.scheme isEqualToString:@"event"]) {
        NSLog(@"jsCallNativeTwo");
    }else{
        NSLog(@"打开页面");
    }
    
    
    //设置请求头:判断请求头是否存在Test字段,如果否,则表示该请求尚未设置请求头
    NSMutableURLRequest *mutableRequest = [navigationAction.request mutableCopy];
    NSDictionary *headFields = mutableRequest.allHTTPHeaderFields;
    if(![headFields objectForKey:@"Test"]){
        NSLog(@"[lilog]:不包含");
        decisionHandler(WKNavigationActionPolicyCancel);
        //如果Test不存在会创建,存在会更新Test对应的value Test:"test"
        [mutableRequest setValue:@"test" forHTTPHeaderField:@"Test"];
        //如果Test不存在会创建,存在会追加Test对应的value Test:"test,add"
        [mutableRequest addValue:@"add" forHTTPHeaderField:@"Test"];
        [webView loadRequest:mutableRequest];
    }else{
        NSLog(@"[lilog]:包含");
        decisionHandler(WKNavigationActionPolicyAllow);
    }
    
    //允许跳转
    //decisionHandler(WKNavigationActionPolicyAllow);
    
    //不允许跳转
    //decisionHandler(WKNavigationActionPolicyCancel);
}


// 开始加载请求,进入 provisional 状态
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
    NSLog(@"----页面开始加载:%@",webView.URL);
}


// 如果有 301/302 重定向会调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
  NSLog(@"----接收到服务器跳转请求之后调用:%@",webView.URL);
  /**
  它可能并不会被所有的重定向触发。在某些情况下,WKWebView 会自动处理重定向,而不会通知到应用程序的 WKNavigationDelegate。

  在你的例子中,百度的网址 http://www.baidu.com 确实会被重定向到 https://www.baidu.com,
  但是这个重定向可能在 WKWebView 内部处理,而不会触发 didReceiveServerRedirectForProvisionalNavigation 方法。

  如果你需要处理这种重定向,可以考虑使用 WKNavigationDelegate 的其他方法,比如 didCommitNavigation 或 didFinishNavigation。
  在这些方法中,你可以检查实际加载的 URL,以便确定是否发生了重定向。
  */
}

// 收到服务器响应 Header,是否继续处理?
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {

    if (navigationResponse.forMainFrame) {
      NSLog(@"👉 主框架响应: %@", navigationResponse.response.URL.absoluteString);
    } else {
      NSLog(@"👉 iframe 响应: %@", navigationResponse.response.URL.absoluteString);
    }
    
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)navigationResponse.response;
    if (httpResponse.statusCode == 200) {
        // 允许加载
        decisionHandler(WKNavigationResponsePolicyAllow);
    } else {
        // 取消加载
        decisionHandler(WKNavigationResponsePolicyCancel);
    }
}


// 内容开始返回并提交到 WebView
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
    NSLog(@"----页面返回内容:%@",webView.URL);
    // 检查当前加载的URL是否与原始请求的URL不同,即发生了重定向
    if (![webView.URL.absoluteString isEqualToString:@"http://www.baidu.com"]) {
        NSLog(@"----重定向到: %@", webView.URL);
        // 在这里可以执行相应的操作
    }
}


// 页面加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    // 加载完成后的处理,可以用于更新界面或执行其他操作
    NSLog(@"----页面渲染完成:%@",webView.URL);
}


// 加载失败时会走这里
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"----页面加载失败:%@",webView.URL);
}





// WKWebView 认证挑战处理模版
- (void)webView:(WKWebView *)webView
  didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
  NSURLCredential * _Nullable credential))completionHandler {

  NSString *authMethod = challenge.protectionSpace.authenticationMethod;

  // -------------------------------
  // 1. HTTPS 自签名证书或不信任证书
  // -------------------------------
  if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  // ⚠️ 注意:仅测试环境使用,生产环境请验证证书
  NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
  completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  return;
  }

  // -------------------------------
  // 2. HTTP Basic / Digest 认证
  // -------------------------------
  if ([authMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic] ||
  [authMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest]) {

  // 这里填写你的用户名和密码
  NSURLCredential *credential = [NSURLCredential credentialWithUser:@"username"
  password:@"password"
  persistence:NSURLCredentialPersistenceForSession];
  completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  return;
  }

  // -------------------------------
  // 3. 客户端证书认证(双向 TLS)
  // -------------------------------
  if ([authMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
  // 加载客户端证书文件(.p12)
  NSString *certPath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
  NSData *p12Data = [NSData dataWithContentsOfFile:certPath];

  CFDataRef inP12data = (__bridge CFDataRef)p12Data;
  SecIdentityRef identity = NULL;
  SecTrustRef trust = NULL;
  [self extractIdentityAndTrust:inP12data identity:&identity trust:&trust]; // 自定义方法

  NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity
  certificates:nil
  persistence:NSURLCredentialPersistenceForSession];
  completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  return;
  }

  // -------------------------------
  // 4. 默认处理
  // -------------------------------
  completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}

  // 辅助方法:提取客户端证书
- (void)extractIdentityAndTrust:(CFDataRef)p12Data identity:(SecIdentityRef *)identity trust:(SecTrustRef *)trust {
  CFStringRef password = CFSTR("你的证书密码");
  const void *keys[] = { kSecImportExportPassphrase };
  const void *values[] = { password };
  CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

  CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
  OSStatus status = SecPKCS12Import(p12Data, options, &items);
  if (status == errSecSuccess && CFArrayGetCount(items) > 0) {
  CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
  *identity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
  }

  if (options) CFRelease(options);
  if (items) CFRelease(items);
}

** 2、WKUIDelegate **
主要处理JS脚本,确认框,警告框等。

// 当 H5 执行 window.close() 方法,则会执行这个代理方法。
- (void)webViewDidClose:(WKWebView *)webView {
    
}


- (nullable WKWebView *)webView:(WKWebView *)webView
 createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
            forNavigationAction:(WKNavigationAction *)navigationAction
                 windowFeatures:(WKWindowFeatures *)windowFeatures {
    
    /**
     在使用WKWebview时发现有些三方页面点击链接不跳转的问题,查阅相关资料后发现,
     用户点击网页上的链接,需要打开新页面时,将先调用decidePolicyForNavigationAction方法,
     其中的WKNavigationAction有两个属性sourceFrame和targetFrame,类型是WKFrameInfo,
     WKFrameInfo的mainFrame属性标记着这个frame是在主frame里还是新开一个frame。
     
     如果 targetFrame的mainFrame属性为NO,将会新开一个页面,
     WKWebView遇到这种情况,将会调用它的WKUIDelegate代理中的createWebViewWithConfiguration方法,
     所以如果我们不实现这个协议就会出现点击无反应的情况,因此对于这种情况需要特殊处理,可以采取下边的方法:
     相当于放弃原来页面直接打开新的页面。
     
     // 不会调用这个方法
     <a href="https://www.baidu.com">跳转到百度,当前页面打开</a><br>
     
     // 在新的标签也中打开时,会调用这个方法
     <a href="https://www.baidu.com" target="_blank">跳转到百度,新页面打开</a>
     */
    WKFrameInfo *frameInfo = navigationAction.targetFrame;
    if (![frameInfo isMainFrame]) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}


/**
 WKUIDelegate是web界面中有弹出警告框时调用这个代理方法,主要是用来处理使用系统的弹框来替换JS中的一些弹框的,比如: 警告框, 选择框, 输入框等
 webView中弹出警告框时调用, 只能有一个按钮
 @param webView webView
 @param message 提示信息
 @param frame 可用于区分哪个窗口调用的
 @param completionHandler 警告框消失的时候调用, 回调给JS
 */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];
    completionHandler();
}

/** 对应js的confirm方法
 webView中弹出选择框时调用, 两个按钮
 @param webView webView description
 @param message 提示信息
 @param frame 可用于区分哪个窗口调用的
 @param completionHandler 确认框消失的时候调用, 回调给JS, 参数为选择结果: YES or NO
 */
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    //    DLOG(@"msg = %@ frmae = %@",message,frame);
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }])];
    
    [self presentViewController:alertController animated:YES completion:nil];
}


/** 对应js的prompt方法
 webView中弹出输入框时调用, 两个按钮 和 一个输入框
 JavaScript调用prompt方法后回调的方法 prompt是js中的输入框 需要在block中把用户输入的信息传入
 @param webView webView description
 @param prompt 提示信息
 @param defaultText 默认提示文本
 @param frame 可用于区分哪个窗口调用的
 @param completionHandler 输入框消失的时候调用, 回调给JS, 参数为输入的内容
 */
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler {
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"请输入" message:prompt preferredStyle:(UIAlertControllerStyleAlert)];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.placeholder = defaultText;
    }];
    
    // 确认按钮
    UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
        UITextField *tf = [alert.textFields firstObject];
        completionHandler(tf.text);
    }];
    
    // 取消按钮
    UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:(UIAlertActionStyleCancel) handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(defaultText);
    }];
    [alert addAction:ok];
    [alert addAction:cancel];
    [self presentViewController:alert animated:YES completion:nil];
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.