美文网首页
WKWebView学习笔记

WKWebView学习笔记

作者: 寻心_0a46 | 来源:发表于2019-08-25 21:47 被阅读0次

    WKWebView

    WKWebView 是苹果在iOS 8中引入的新组件,用于显示交互式web内容的对象,支持更多的HTML5的特性,其官方宣称高达60fps的滚动刷新率以及内置手势,并将UIWebViewDelegate与UIWebView拆分成了14类与3个协议,拥有Safari相同的JavaScript引擎,可以更少的占用内存。

    使用WKWebView类将web内容嵌入到应用程序中时,需要创建一个WKWebView对象,并将它设置为视图,然后向其发送加载web内容的请求。使用initWithFrame:configuration: 函数创建一个新的WKWebView对象,使用loadHTMLString:baseURL: 函数加载本地HTML文件,或者使用loadRequest:函数加载web内容。使用loading属性来确定web视图是否正在加载,并且stopLoading方法来停止加载。将delegate属性设置为一个符合WKUIDelegate协议的对象,以跟踪web内容的加载。如果要让用户在网页历史中向前和向后移动,可以使用goBack和forward方法作为按钮的操作。当用户无法向某个方向移动时,使用canGoBack和canGoForward属性禁用按钮。默认情况下,web视图会自动将出现在web内容中的电话号码转换为电话链接。当点击电话链接时,手机应用程序启动并拨打电话号码。可以使用dataDetectorTypes设置此默认行为。还可以使用setMagnification:centeredAtPoint: 函数以编程方式设置web内容第一次在web视图中显示时的规模。此后,用户可以使用手势改变比例。

    常用属性

    @property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;

    属性描述 :只读属性, 用于初始化web视图的配置的副本。

    @property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;
    

    @property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;

    属性描述 : web视图的导航代理。

    @property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
    

    @property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;

    属性描述:web视图的用户界面代理。

    @property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
    

    @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;

    属性描述 : web视图的前向列表。

    @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
    

    @property (nullable, nonatomic, readonly, copy) NSString *title;

    属性描述 : 页面标题。这个属性是兼容WKWebView类的键值观察(KVO)的。

    @property (nullable, nonatomic, readonly, copy) NSString *title;
    

    @property (nullable, nonatomic, readonly, copy) NSURL *URL;

    属性描述 : 活跃的URL,即要在用户界面中反映的URL。这个属性是兼容WKWebView类的键值观察(KVO)的。

    @property (nullable, nonatomic, readonly, copy) NSURL *URL;
    

    @property (nonatomic, readonly, getter=isLoading) BOOL loading;

    属性描述 : 一个布尔值,指示当前Web视图当前是否正在加载内容。如果接收器仍在加载内容,则设置为true;否则设置为false。这个属性是兼容WKWebView类的键值观察(KVO)的。

    @property (nonatomic, readonly, getter=isLoading) BOOL loading;
    

    @property (nonatomic, readonly) double estimatedProgress;

    属性描述 : 预估当前导航已加载的比例。这个值的范围在0.0到1.0之间,这取决于预计接收到的字节总数,包括主文档及其所有潜在的子资源。导航加载完成后,估计进度保持在1.0,直到新的导航开始,这时估计进度被重置为0.0。这个属性是兼容WKWebView类的键值观察(KVO)的。

    @property (nonatomic, readonly) double estimatedProgress;
    

    例如:监听estimatedProgress属性键值观察的代码片段:

    //为webView添加观察者
    [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
    
    //观察者方法,监听webView加载进度,调整进度条百分比
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:@"estimatedProgress"]) {
            //estimatedProgress当前导航的网页已经加载的估计值(double:0.0~1.0)
            [self.progressView setProgress:self.webView.estimatedProgress animated:YES];
            self.progressView.progress = self.webView.estimatedProgress;
            if (self.webView.estimatedProgress == 1.0) {
                [self.progressView removeFromSuperview];
            }
        }
    }
    

    @property (nonatomic, readonly) BOOL hasOnlySecureContent;

    属性描述 : 一个布尔值,指示是否已通过安全加密的连接加载页上的所有资源。这个属性是兼容WKWebView类的键值观察(KVO)的。

    @property (nonatomic, readonly) BOOL hasOnlySecureContent;
    

    @property (nonatomic, readonly) BOOL canGoBack;

    属性描述 : 一个布尔值,指示“后退”列表中是否有可导航到的“后退”项。通常用于后退前进行判断。

    @property (nonatomic, readonly) BOOL canGoBack;
    

    例如:根据canGoBack判断web视图是后退到上一级或是关闭web视图:

    - (void)goBack {
        //如果可以返回
        if([self.webView canGoBack]) {
            //进行返回
            [self.webView goBack];
    
        }else{
            //弹出当前视图控制器
            [self.navigationController popViewControllerAnimated:YES];
        }
    }
    

    @property (nonatomic, readonly) BOOL canGoForward;

    属性描述 : 一个布尔值,指示“前进”列表中是否有可导航到的前进项。

    @property (nonatomic, readonly) BOOL canGoForward;
    

    @property (nonatomic) BOOL allowsBackForwardNavigationGestures;

    属性描述 : 一个布尔值,指示水平滑动手势是否会触发前后列表导航。默认值为“NO”。

    @property (nonatomic) BOOL allowsBackForwardNavigationGestures;
    
    常用函数

    - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

    函数描述 : 返回用指定的框架矩形和配置初始化的web视图。接受自定义WKWebViewConfiguration的指定初始值设定项。初始值设定项复制指定的配置。初始化web视图后更改配置对象对web视图没有影响。也可以使用initWithFrame:方法用默认配置初始化一个实例。

    参数 :

    frame : 新web视图的框架矩形。

    configuration : 新web视图的配置。

    返回值 : 初始化的web视图,如果对象无法初始化,则为nil。

    - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
    

    - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;

    函数描述 : 导航到请求的URL。

    - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
    

    - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

    函数描述 : 设置网页内容和基本URL。

    参数 :

    string : 用作网页内容的字符串。

    baseURL :用于解析文档中的相对URL的URL。

    返回值:一个新的WKWebView导航。

    - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
    

    例如:一个加载本地Html文件的极其简单的例子:

    首先需要创建一个Html文件,如图:

    截屏2021-02-03下午10.21.57.png 截屏2021-02-03下午10.22.46.png

    极其简单的Html文件:

    <!DOCTYPE html>
    <html>
        <head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
            <meta content="telephone=no,email=no,address=no" name="format-detection">
            <meta name="viewport\" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">
            <meta name="msapplication-tap-highlight" content="no">
            <meta name="apple-mobile-web-app-capable\" content="yes">
            <meta name="wap-font-scale\" content=\"no\">
        </head>
        <body>
            <h1>第一次练习</h1>
        </body>
    </html>
    
    

    显示WKWebView视图:

    #import "TestWebViewAndJSInteractiveController.h"
    
    @interface TestWebViewAndJSInteractiveController ()
    
    @property (nonatomic, strong) WKWebView *webView;
    
    @end
    
    @implementation TestWebViewAndJSInteractiveController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self createUI];
    }
    
    - (void)createUI{
        
        //初始化WKWebView
        self.webView = [[WKWebView alloc]init];
        //设置WKWebView背景色
        self.webView.backgroundColor = HEXCOLOR(0xEEF2F3);
        //添加WKWebView
        [self.view addSubview:self.webView];
        [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(self.view);
        }];
        //初始化异常
        NSError *error = nil;
        //读取正在运行的应用程序的捆绑目录中对应resource名称的文件,并使用给定的编码格式解析为字符串
        NSString *htmlString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"TestInteractive.html" ofType:nil] encoding:NSUTF8StringEncoding error:&error];
        //设置网页内容
        [self.webView loadHTMLString:htmlString baseURL:nil];
    }
    
    @end
    

    看到页面就是阶段性的胜利:

    截屏2021-02-03下午10.34.23.png

    - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

    函数描述 : 执行JavaScript字符串。

    参数 :

    javaScriptString : 要执行的JavaScript字符串。

    completionHandler : 脚本执行完成或失败时要调用的块。

    - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
    

    WKWebViewConfiguration

    WKWebViewConfiguration是WKWebView的配置类,实例化WKWebViewConfiguration被访问其中的属性设置配置信息,在实例化WKWebView时将WKWebViewConfiguration实例作为参数传入。

    例如:使用WKWebViewConfiguration携带偏好设置初始化WKWebView视图的代码片段:

    //初始化一个WKWebViewConfiguration对象
    WKWebViewConfiguration *config = [WKWebViewConfiguration new];
    //初始化偏好设置属性:preferences
    config.preferences = [WKPreferences new];
    //默认情况下,最小字体大小为0;
    config.preferences.minimumFontSize = 0;
    //是否支持JavaScript
    config.preferences.javaScriptEnabled = YES;
    //不通过用户交互,是否可以打开窗口
    config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
    // 检测各种特殊的字符串:比如电话、网站
    config.dataDetectorTypes = UIDataDetectorTypeAll;
    // 播放视频
    config.allowsInlineMediaPlayback = YES;
    
    // 交互对象设置
    config.userContentController = [[WKUserContentController alloc] init];
            
    WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
    [self.view addSubview:webView];
    
    常用属性

    @property (nonatomic, strong) WKProcessPool *processPool;

    属性描述 : 流程池,从中获取视图的web内容流程。初始化web视图时,将从指定的池中为其创建新的web内容流程,或者使用该池中的现有流程。

    @property (nonatomic, strong) WKProcessPool *processPool;
    

    @property (nonatomic, strong) WKPreferences *preferences;

    属性描述 : 加载web视图要使用的偏好设置。

    @property (nonatomic, strong) WKPreferences *preferences;
    

    @property (nonatomic, strong) WKUserContentController *userContentController;

    属性描述 : 内容交互控制器,自己注入JS代码及JS调用原生方法注册,在delloc时需要移除注入

    @property (nonatomic, strong) WKUserContentController *userContentController;
    

    @property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore;

    属性描述 : 网站数据储存对象,这个属性根据需求选择使用默认或自己设置,使用这个对象可以设置不存储任何数据和移除/获取缓存数据

    @property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore;
    

    @property (nullable, nonatomic, copy) NSString *applicationNameForUserAgent;

    属性描述 : 以字符串告知把告知浏览器一些硬件的信息。

    例如 : webConfig.applicationNameForUserAgent = @"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)";

    @property (nullable, nonatomic, copy) NSString *applicationNameForUserAgent;
    

    @property (nonatomic) BOOL allowsAirPlayForMediaPlayback;

    属性描述 : 一个布尔值,指示是否允许AirPlay播放。默认值是YES。

    @property (nonatomic) BOOL allowsAirPlayForMediaPlayback;
    

    @property (nonatomic) WKAudiovisualMediaTypes mediaTypesRequiringUserActionForPlayback;

    属性描述 : 确定哪些媒体类型需要用户手势才能开始播放

    //WKAudiovisualMediaTypes枚举值如下:
    typedef NS_OPTIONS(NSUInteger, WKAudiovisualMediaTypes) {
        WKAudiovisualMediaTypeNone = 0,
        WKAudiovisualMediaTypeAudio = 1 << 0,
        WKAudiovisualMediaTypeVideo = 1 << 1,
        WKAudiovisualMediaTypeAll = NSUIntegerMax
    }
    
    @property (nonatomic) WKAudiovisualMediaTypes mediaTypesRequiringUserActionForPlayback;
    

    @property (nonatomic) BOOL allowsInlineMediaPlayback;

    属性描述 : 一个布尔值,设置HTML5视频是否允许网页播放 ,设置为NO则会使用本地播放器。默认值是NO。

    @property (nonatomic) BOOL allowsInlineMediaPlayback;
    

    @property (nonatomic) WKSelectionGranularity selectionGranularity;

    属性描述 : 用户可以交互选择web视图中的内容的粒度级别。可能的值在WKSelectionGranularity中描述。默认值是WKSelectionGranularityDynamic。

    //WKSelectionGranularity枚举值如下:
    typedef NS_ENUM(NSInteger, WKSelectionGranularity) {
        WKSelectionGranularityDynamic,
        WKSelectionGranularityCharacter,
    }
    
    @property (nonatomic) WKSelectionGranularity selectionGranularity;
    

    @property (nonatomic) BOOL allowsPictureInPictureMediaPlayback;

    属性描述 : 是否允许画中画技术

    @property (nonatomic) BOOL allowsPictureInPictureMediaPlayback;
    

    @property (nonatomic) WKDataDetectorTypes dataDetectorTypes;

    属性描述 : 表示所需数据检测类型的枚举值。默认值是WKDataDetectorTypeNone。默认情况下,web 视图会自动将出现在 web 内容中的电话号码,链接,日历转换为链接,当链接被点击时,
    程序就会拨打该号码或打开日历或链接,要关闭这个默认的行为,用 WKDataDetectorTypes 设置
    WKDataDetectorTypePhoneNumber | WKDataDetectorTypeLink | WKDataDetectorTypeCalendarEvent。

    @property (nonatomic) WKDataDetectorTypes dataDetectorTypes;
    

    @property (nonatomic) BOOL suppressesIncrementalRendering;

    属性描述 : 一个布尔值,指示web视图是否会抑制内容呈现,直到将其完全加载到内存中在渲染视图。默认值是NO。

    @property (nonatomic) BOOL suppressesIncrementalRendering;
    

    @property (nonatomic) BOOL ignoresViewportScaleLimits;

    属性描述 : 一个布尔值,指示WkWebView是否应始终允许缩放网页,而不管作者的意图如何。这将覆盖用户可伸缩属性。默认值为否。

    @property (nonatomic) BOOL ignoresViewportScaleLimits;
    

    @property (nonatomic) WKUserInterfaceDirectionPolicy userInterfaceDirectionPolicy;

    属性描述 : 用户界面元素的方向性。在WKUserInterfaceDirectionPolicy中描述了可能的值。默认值是WKUserInterfaceDirectionPolicyContent。

    //WKUserInterfaceDirectionPolicy枚举值如下:
    typedef NS_ENUM(NSInteger, WKUserInterfaceDirectionPolicy) {
        WKUserInterfaceDirectionPolicyContent,
        WKUserInterfaceDirectionPolicySystem,
    } 
    
    @property (nonatomic) WKUserInterfaceDirectionPolicy userInterfaceDirectionPolicy;
    

    WKPreferences

    WKPreferences设置加载web视图要使用的偏好设置

    常用属性

    @property (nonatomic) CGFloat minimumFontSize;

    属性描述 : 设置最小字体,默认值为0

    @property (nonatomic) CGFloat minimumFontSize;
    

    @property (nonatomic) BOOL javaScriptEnabled;

    属性描述 : 是否启用 javaScript,默认值为YES

    @property (nonatomic) BOOL javaScriptEnabled;
    

    @property (nonatomic) BOOL javaScriptCanOpenWindowsAutomatically;

    属性描述 : javascript是否可以打开没有用户交互的窗口,默认值为NO

    @property (nonatomic) BOOL javaScriptCanOpenWindowsAutomatically;
    

    @property (nonatomic) BOOL javaEnabled;

    属性描述 : 是否启用java 默认为NO。

    @property (nonatomic) BOOL javaEnabled;
    

    @property (nonatomic) BOOL plugInsEnabled;

    属性描述 : 是否启用插件,默认为NO

    @property (nonatomic) BOOL plugInsEnabled;
    

    @property (nonatomic) BOOL tabFocusesLinks API_AVAILABLE(macosx(10.12.3));

    属性描述 : 如果tabFocusesLinks是YES,那么tab键将集中链接和表单控件。Option键暂时反转此首选项

    @property (nonatomic) BOOL tabFocusesLinks API_AVAILABLE(macosx(10.12.3));
    

    WKUserScript

    WKUserScript 对象表示可以注入到网页中的脚本。

    常用函数

    - (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;

    函数描述 : 返回可以添加WKUserContentController中的初始化用户脚本。

    参数 :

    source :脚本源。

    injectionTime : 应该注入脚本的时间。

    forMainFrameOnly : 脚本是应注入全局窗口(NO)或只限主窗口(yes)

    - (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
    
    常用属性

    @property (nonatomic, readonly, copy) NSString *source;

    属性描述 : 脚本源代码

    @property (nonatomic, readonly, copy) NSString *source;
    

    @property (nonatomic, readonly) WKUserScriptInjectionTime injectionTime;

    属性描述 : 脚本应该被注入网页中的时间点

    WKUserScriptInjectionTime枚举值如下:
    typedef NS_ENUM(NSInteger, WKUserScriptInjectionTime) {
        // 在文档元素创建之后,但在加载任何其他内容之前注入脚本。
        WKUserScriptInjectionTimeAtDocumentStart,
       //在文件完成加载后,但在其他子资源完成加载之前注入该脚本。
        WKUserScriptInjectionTimeAtDocumentEnd
    } API_AVAILABLE(macosx(10.10), ios(8.0));
    
    @property (nonatomic, readonly) WKUserScriptInjectionTime injectionTime;
    

    @property (nonatomic, readonly, getter=isForMainFrameOnly) BOOL forMainFrameOnly;

    属性描述 : 布尔值,指示脚本是否仅应注入主窗口(是)或所有窗口(否)

    @property (nonatomic, readonly, getter=isForMainFrameOnly) BOOL forMainFrameOnly;
    

    WKUserContentController

    WKUserContentController对象提供了一种JavaScript向web视图发送消息的方法。与web视图关联的用户内容控制器由其web视图配置指定。

    常用属性

    @property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;

    属性描述 : 与此用户内容控制器关联的用户脚本。

    @property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
    
    常用函数

    - (void)addUserScript:(WKUserScript *)userScript;

    函数描述 : 添加用户脚本

    参数 :

    userScript : 要添加的用户脚本

    - (void)addUserScript:(WKUserScript *)userScript;
    

    - (void)removeAllUserScripts;

    函数描述 : 删除所有关联的用户脚本

    - (void)removeAllUserScripts;
    

    - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

    函数描述 : 注册要被js调用的方法名称,添加scriptmessagehandler将为所有窗口添加一个函数window.webkit.messagehandlers.<name>.postmessage(<messagebody>)。

    之后在JavaScript中使window.webkit.messageHandlers.name.postMessage()方法来像native发送消息,支持OC中字典,数组,NSNumber等原生数据类型,JavaScript代码中的name要和上面注册的相同。

    在native代理的-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;回调方法中,会获取到JavaScript传递进来的消息

    参数 :

    scriptMessageHandler : 要添加的消息处理程序。

    name :消息处理程序的名称。

    - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
    

    - (void)removeScriptMessageHandlerForName:(NSString *)name;

    函数描述 : 删除脚本消息处理程序。

    参数 :

    name : 要删除的消息处理程序的名称

    - (void)removeScriptMessageHandlerForName:(NSString *)name;
    

    - (void)addContentRuleList:(WKContentRuleList *)contentRuleList API_AVAILABLE(macosx(10.13), ios(11.0));

    函数描述 : 添加内容规则。

    参数 :

    contentRuleList : 要添加的规则列表。规则列表是从WKContentExtensionStore对象创建或检索的。

    - (void)addContentRuleList:(WKContentRuleList *)contentRuleList API_AVAILABLE(macosx(10.13), ios(11.0));
    

    - (void)removeContentRuleList:(WKContentRuleList *)contentRuleList API_AVAILABLE(macosx(10.13), ios(11.0));

    函数描述 : 删除内容规则列表

    参数 :

    contentRuleList : 要删除的内容规则列表

    - (void)removeContentRuleList:(WKContentRuleList *)contentRuleList API_AVAILABLE(macosx(10.13), ios(11.0));
    

    - (void)removeAllContentRuleLists API_AVAILABLE(macosx(10.13), ios(11.0));

    函数描述 : 删除所有关联的内容规则列表

    - (void)removeAllContentRuleLists API_AVAILABLE(macosx(10.13), ios(11.0));
    

    例如:JS调用IOS注册的函数

    这里我们可以进阶了,给JS一个机会,让它与IOS进行一次友好的沟通。进阶的Html文件,如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
            <meta content="telephone=no,email=no,address=no" name="format-detection">
            <meta name="viewport\" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">
            <meta name="msapplication-tap-highlight" content="no">
            <meta name="apple-mobile-web-app-capable\" content="yes">
            <meta name="wap-font-scale\" content=\"no\">
        </head>
        <body>
            <h1>第一次练习</h1>
            <input name="but" type="button" value="调用IOS" style="height:80px; width:240px; font-size:40px;" onclick = "javascript:btn_Click()"/>
            
        </body>
        
        <script type='text/javascript'>
        function btn_Click(){
            alert("即将触发事件");
            window.webkit.messageHandlers.jsCallios.postMessage({
                "hello": "你好",
            });
        }
        </script>
    </html>
    
    

    进阶的iOS显示WKWebView的文件,添加了不少好东西,监听了页面加载、监听了Html页面alert弹框展示、监听了JS调用IOS的函数,如下:

    #import "TestWebViewAndJSInteractiveController.h"
    
    @interface TestWebViewAndJSInteractiveController ()<UIScrollViewDelegate,WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>
    
    @property (nonatomic, strong) WKWebView *webView;
    
    @end
    
    @implementation TestWebViewAndJSInteractiveController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self createUI];
    }
    
    - (void)dealloc{
        //删除脚本消息处理程序
        [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"jsCallios"];
    }
    
    
    - (void)createUI{
        //初始化一个WKWebViewConfiguration对象
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
        //初始化偏好设置属性:preferences
        configuration.preferences = [WKPreferences new];
        //默认情况下,最小字体大小为0;
        configuration.preferences.minimumFontSize = 0;
        //是否支持JavaScript
        configuration.preferences.javaScriptEnabled = YES;
        //不通过用户交互,是否可以打开窗口
        configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
        //注册要被js调用的jsCallios方法
        [configuration.userContentController addScriptMessageHandler:self name:@"jsCallios"];
        //初始化WKWebView
        self.webView = [[WKWebView alloc]initWithFrame:CGRectZero configuration:configuration];
        //设置WKWebView滚动视图代理
        self.webView.scrollView.delegate = self;
        //设置WKWebView用户界面委托代理
        self.webView.UIDelegate = self;
        //设置WKWebView导航代理
        self.webView.navigationDelegate = self;
        //设置WKWebView背景色
        self.webView.backgroundColor = HEXCOLOR(0xEEF2F3);
        //添加WKWebView
        [self.view addSubview:self.webView];
        [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(self.view);
        }];
        //初始化异常
        NSError *error = nil;
        //读取正在运行的应用程序的捆绑目录中对应resource名称的文件,并使用给定的编码格式解析为字符串
        NSString *htmlString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"TestInteractive.html" ofType:nil] encoding:NSUTF8StringEncoding error:&error];
        //设置网页内容
        [self.webView loadHTMLString:htmlString baseURL:nil];
    }
    
    #pragma mark -- WKNavigationDelegate
    //监听页面加载状态
    
    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
        NSLog(@"开始加载web页面");
    }
    
    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
        NSLog(@"页面加载完成");
    }
    
    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
        NSLog(@"页面加载失败");
    }
    
    #pragma mark -- WKUIDelegate
    //显示一个alert弹框(iOS展示Html页面alert弹框时使用)
    
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
        
    }
    
    #pragma mark - WKScriptMessageHandler
    //监听js调用注册的函数
    
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
        //判断执行的函数名称
        if ([message.name isEqualToString:@"jsCallios"]) {
            NSDictionary *jsCalliosDic = (NSDictionary *)message.body;
            //获取消息参数
            if([jsCalliosDic.allKeys containsObject:@"hello"]){
                //显示提示
                [self.view makeToast:[jsCalliosDic objectForKey:@"hello"]duration:1.5 position:CSToastPositionCenter];
            }
        }
    }
    
    @end
    

    这次不但能看到页面,按钮还可以调用IOS函数呢:

    Jietu20210203-234137.gif

    例如:IOS调用JS函数

    正所谓来而不往非礼也,既然JS已经调用了IOS函数,IOS怎么能不给JS回应呢,这时候evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler函数就要善良登场了。

    再次进阶的Html文件,增加了显示IOS传递给JS的文本的Div:

    <!DOCTYPE html>
    
    <html>
        
        <head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
            <meta content="telephone=no,email=no,address=no" name="format-detection">
            <meta name="viewport\" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">
            <meta name="msapplication-tap-highlight" content="no">
            <meta name="apple-mobile-web-app-capable\" content="yes">
            <meta name="wap-font-scale\" content=\"no\">
        </head>
        
        <body>
            <h1>第一次练习</h1>
            
            <input name="but" type="button" value="调用IOS" style="height:80px; width:240px; font-size:40px;" onclick = "javascript:btn_Click()"/>
            
            <div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 200px; margin-top:50px;">
            
        </body>
        
        <script type='text/javascript'>
            
        function btn_Click(){
            alert("即将触发事件");
            window.webkit.messageHandlers.jsCallios.postMessage({
                "hello": "你好",
            });
        }
        
        function iosCalljs(message){
            document.getElementById("returnValue").innerHTML = message;
        }
        
        </script>
        
    </html>
    

    以及再次进阶的IOS文件:

    #import "TestWebViewAndJSInteractiveController.h"
    
    @interface TestWebViewAndJSInteractiveController ()<UIScrollViewDelegate,WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>
    
    @property (nonatomic, strong) WKWebView *webView;
    
    @end
    
    @implementation TestWebViewAndJSInteractiveController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.title = @"IOS与JS交互";
        [self createUI];
    }
    
    - (void)dealloc{
        //删除脚本消息处理程序
        [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"jsCallios"];
    }
    
    
    - (void)createUI{
        
        ///调用JS函数的按钮
        UIButton *iosCallJsButton  = [UIButton buttonWithType:UIButtonTypeCustom];
        [self.view addSubview:iosCallJsButton];
        iosCallJsButton.layer.cornerRadius = 22.0;
        iosCallJsButton.layer.masksToBounds = YES;
        iosCallJsButton.titleLabel.font = [UIFont systemFontOfSize:16];
        iosCallJsButton.backgroundColor = [UIColor redColor];
        [iosCallJsButton setTitle:@"调用JS函数" forState:UIControlStateNormal];
        [iosCallJsButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [iosCallJsButton addTarget:self action:@selector(CallJsFunction) forControlEvents:UIControlEventTouchUpInside];
        
        [iosCallJsButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(self.view.mas_bottom).offset(- 10);
            make.left.equalTo(self).offset(10 * 2.0);
            make.right.equalTo(self).offset(-10 * 2.0);
            make.height.mas_equalTo(44);
        }];
        
        //初始化一个WKWebViewConfiguration对象
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
        //初始化偏好设置属性:preferences
        configuration.preferences = [WKPreferences new];
        //默认情况下,最小字体大小为0;
        configuration.preferences.minimumFontSize = 0;
        //是否支持JavaScript
        configuration.preferences.javaScriptEnabled = YES;
        //不通过用户交互,是否可以打开窗口
        configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
        //注册要被js调用的jsCallios方法
        [configuration.userContentController addScriptMessageHandler:self name:@"jsCallios"];
        //初始化WKWebView
        self.webView = [[WKWebView alloc]initWithFrame:CGRectZero configuration:configuration];
        //设置WKWebView滚动视图代理
        self.webView.scrollView.delegate = self;
        //设置WKWebView用户界面委托代理
        self.webView.UIDelegate = self;
        //设置WKWebView导航代理
        self.webView.navigationDelegate = self;
        //设置WKWebView背景色
        self.webView.backgroundColor = HEXCOLOR(0xEEF2F3);
        //添加WKWebView
        [self.view addSubview:self.webView];
        [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(iosCallJsButton.mas_top);
            make.top.left.right.equalTo(self.view);
        }];
        //初始化异常
        NSError *error = nil;
        //读取正在运行的应用程序的捆绑目录中对应resource名称的文件,并使用给定的编码格式解析为字符串
        NSString *htmlString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"TestInteractive.html" ofType:nil] encoding:NSUTF8StringEncoding error:&error];
        //设置网页内容
        [self.webView loadHTMLString:htmlString baseURL:nil];
    }
    
    ///调用JS函数
    - (void)CallJsFunction{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.webView evaluateJavaScript:@"iosCalljs('IOS给您拜年了')" completionHandler:^(id response, NSError *error) {
                NSLog(@"%@",error);
            }];
        });
    }
    
    #pragma mark -- WKNavigationDelegate
    //监听页面加载状态
    
    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
        NSLog(@"开始加载web页面");
    }
    
    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
        NSLog(@"页面加载完成");
    }
    
    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
        NSLog(@"页面加载失败");
    }
    
    #pragma mark -- WKUIDelegate
    //显示一个alert弹框(iOS展示Html页面alert弹框时使用)
    
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
        
    }
    
    #pragma mark - WKScriptMessageHandler
    //监听js调用注册的函数
    
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
        //判断执行的函数名称
        if ([message.name isEqualToString:@"jsCallios"]) {
            NSDictionary *jsCalliosDic = (NSDictionary *)message.body;
            //获取消息参数
            if([jsCalliosDic.allKeys containsObject:@"hello"]){
                //显示提示
                [self.view makeToast:[jsCalliosDic objectForKey:@"hello"]duration:1.5 position:CSToastPositionCenter];
            }
        }
    }
    
    @end
    

    效果如图 :

    Jietu20210205-003635.gif

    UIProgressView

    UIProgressView用户界面进度视图

    初始化函数

    - (instancetype)initWithProgressViewStyle:(UIProgressViewStyle)style;

    函数描述 : 初始化进度条,并设置进度条样式

    //UIProgressViewStyle枚举值如下:
    typedef NS_ENUM(NSInteger, UIProgressViewStyle) {
        UIProgressViewStyleDefault,     // 正常的进度条
        UIProgressViewStyleBar __TVOS_PROHIBITED,     // 用于工具栏
    };
    
    - (instancetype)initWithProgressViewStyle:(UIProgressViewStyle)style;
    
    常用属性

    @property(nonatomic) float progress;

    属性描述 : 接收器显示的当前进度。当前进度由0.0到1.0(包括1.0)之间的浮点值表示,其中1.0表示任务的完成。默认值是0.0。小于0.0和大于1.0的值都受到这些限制。

    @property(nonatomic) float progress;
    

    @property(nonatomic, strong, nullable) UIColor* progressTintColor

    属性描述 : 设置进度条颜色

    @property(nonatomic, strong, nullable) UIColor* progressTintColor
    

    @property(nonatomic, strong, nullable) UIColor* trackTintColor

    属性描述 : 设置进度条未加载位置颜色

    @property(nonatomic, strong, nullable) UIColor* trackTintColor
    

    @property(nonatomic, strong, nullable) UIImage* progressImage

    属性描述 : 设置进度条图片

    @property(nonatomic, strong, nullable) UIImage* progressImage
    

    @property(nonatomic, strong, nullable) UIImage* trackImage

    属性描述 : 设置进度条未加载位置图片

    @property(nonatomic, strong, nullable) UIImage* trackImage
    

    WKNavigationDelegate

    WKNavigationDelegate 协议方法可以帮助你实现在Web视图接受,加载和完成导航请求的过程中触发的自定义行为。

    常用函数

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

    函数描述 : 在发送请求之前,决定是否允许或取消导航。如果不实现此方法,web视图将加载请求,或者(如果合适的话)将请求转发给另一个应用程序。

    参数 :

    webView : 调用委托方法的web视图。

    navigationAction : 关于触发导航请求的操作的描述信息。

    decisionHandler : 要调用的决策处理程序,以允许或取消导航。参数是枚举类型WKNavigationActionPolicy的常量之一。

    //WKNavigationActionPolicy枚举值如下:
    typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
        WKNavigationActionPolicyCancel, //取消导航
        WKNavigationActionPolicyAllow, //允许导航继续
    } API_AVAILABLE(macosx(10.10), ios(8.0));
    
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
    

    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

    函数描述 : 页面开始加载web内容时调用

    参数 :

    webView : 调用委托方法的web视图

    navigation : 开始加载页的导航对象。

    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
    

    - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;

    函数描述 : 当web内容开始返回时调用。

    参数 :

    webView : 调用委托方法的web视图。

    navigation : 开始加载页的导航对象。

    - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
    

    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;

    函数描述 : 页面加载完成之后调用。

    参数 :

    webView : 调用委托方法的web视图。

    navigation : 完成的导航对象。

    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
    

    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;

    函数描述 : 页面加载失败时调用 (web视图加载内容时发生错误)。

    参数 :

    webView : 调用委托方法的web视图。

    navigation : 开始加载页的导航对象。

    error : 发生的错误。

    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
    

    - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;

    函数描述 : web视图导航过程中发生错误时调用。

    参数 :

    webView : 调用委托方法的web视图。

    navigation : 开始加载页的导航对象。

    error : 发生的错误。

    - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
    

    - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

    函数描述 : 当web视图收到服务器重定向时调用。

    参数 :

    webView : 调用委托方法的web视图。

    navigation : 接收到服务器重定向的导航对象。

    - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
    

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

    函数描述 : 在收到响应之后决定是否跳转。

    参数 :

    webView :调用委托方法的web视图。

    navigationResponse : 有关导航响应的描述性信息。

    decisionHandler :当应用程序决定是否允许或取消导航时要调用的块。块采用单个参数,该参数必须是枚举类型WKNavigationResponsePolicy的常量之一。

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
    

    - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));

    函数描述 : 当Web视图的Web内容进程终止时调用。

    参数 :

    webView : 调用委托方法的web视图。

    - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));
    

    - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

    函数描述 : 在web视图需要响应身份验证时调用。如果不实现此方法,web视图将使用nsurlsessionauthchallenge erejectprotectionspace配置来响应身份验证。

    参数 :

    webView : 接受身份验证的web视图。

    challenge : 身份验证。

    completionHandler : 必须调用完成处理程序才能响应该挑战。配置参数是枚举类型的常量之一 NSURLSessionAuthChallengeDisposition

    //NSURLSessionAuthChallengeDisposition枚举值如下:
    typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) {
        /*使用指定的凭据,它可以是nil*/
        NSURLSessionAuthChallengeUseCredential = 0,
        /*对验证要求的默认处理-就像未实现此委托一样;忽略凭据参数 */                                       
        NSURLSessionAuthChallengePerformDefaultHandling = 1,    
        /*整个请求将被取消;凭证参数将被忽略 */                          
        NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,
       /* 此验证要求被拒绝,应尝试下一个身份验证保护空间;忽略凭据参数 */                       
        NSURLSessionAuthChallengeRejectProtectionSpace = 3,                               
    } NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);
    
    - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
    

    例如 :当使用 Https 协议加载web内容时,使用的证书不合法或者证书过期时:

    - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([challenge previousFailureCount] == 0) {
                NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
            } else {
                completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
            }
        } else {
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
        }
    }
    

    WKScriptMessageHandler代理提供的函数

    符合WKScriptMessageHandler协议的类提供了一种方法,用于从网页中运行的JavaScript接收消息

    常用函数

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

    函数描述 : 当从网页接收到脚本消息时调用。

    参数 :

    userContentController : 调用委托方法的WKUserContentController

    message : 收到的脚本消息

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
    

    WKUIDelegate

    WKUIDelegate 类是网页视图的用户界面委托协议,提供了代表网页呈现本机用户界面元素的方法。

    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;

    函数描述 : 显示JavaScript警告面板。为了用户的安全,你的应用程序应该注意一个特定的网站控制这个面板的内容。识别控制网站的简单forumla是frame.request.URL.host。面板应该只有一个OK按钮。如果不实现此方法,web视图的行为将与用户选择OK按钮一样。

    参数 :

    webView : 调用委托方法的web视图。

    message : 要显示的消息。

    frame : 有关JavaScript发起此调用的窗口的信息。

    completionHandler : 在警报面板被取消后调用的完成处理程序。

    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
    

    例如:显示一个 JavaScript 警告弹窗:

    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
        
    }
    

    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;

    函数描述 : 显示一个 JavaScript 确认面板。为了用户安全,您的应用程序应该注意这样一个事实,即一个特定的网站控制了这个面板中的内容。识别控制网站的简单forumla是frame.request.URL.host。面板应该有两个按钮,比如OK和Cancel。如果不实现此方法,web视图的行为将与用户选择Cancel按钮一样。

    参数 :

    webView : 调用委托方法的web视图。

    message : 要显示的消息。

    frame : 有关JavaScript发起此调用的窗口的信息。

    completionHandler : 在确认面板被取消后调用的完成处理程序。如果用户选择OK,则传递YES;如果用户选择Cancel,则传递NO。

    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
    

    例如:显示一个 JavaScript 确认面板:

    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
        
        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];
    }
    

    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;

    函数描述 : 显示一个 JavaScript 文本输入面板。为了用户安全,您的应用程序应该注意这样一个事实,即一个特定的网站控制了这个面板中的内容。识别控制网站的简单forumla是frame.request.URL.host。面板应该有两个按钮,比如OK和Cancel,以及一个输入文本的字段。如果不实现此方法,web视图的行为将与用户选择Cancel按钮一样。

    参数 :

    webView : 调用委托方法的web视图。

    prompt : 显示提示符。

    defaultText:要在文本输入字段中显示的初始文本。

    frame : 有关JavaScript发起此调用的窗口的信息。

    completionHandler :完成处理程序调用后的文本,输入面板已被取消。如果用户选择OK,则传递输入的文本,否则为nil。

    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
    

    例如:显示一个 JavaScript 文本输入面板:

    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
            textField.text = defaultText;
        }];
        [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler(alertController.textFields[0].text?:@"");
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
    }
    
    

    使用示例代码:

    //
    //  ViewController.h
    
    #import <UIKit/UIKit.h>
    
    @interface ViewController : UIViewController
    
    @end
    
    
    //
    //  ViewController.m
    
    #import "ViewController.h"
    #import "Masonry.h"
    #import "BaseWebViewControlle.h"
    
    //十六进制颜色宏定义
    #define HEXCOLOR(c)                                  \
    [UIColor colorWithRed:((c >> 16) & 0xFF) / 255.0 \
    green:((c >> 8) & 0xFF) / 255.0  \
    blue:(c & 0xFF) / 255.0         \
    alpha:1.0]
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        self.navigationItem.title = @"百度一下,你就知道";
        [self setButton];
    }
    
    - (void)setButton{
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button setTitle:@"百度一下" forState:UIControlStateNormal];
        button.titleLabel.font = [UIFont systemFontOfSize:15];
        [button addTarget:self action:@selector(goBaiDu) forControlEvents:UIControlEventTouchUpInside];
        
        [self.view addSubview:button];
        [button mas_makeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(CGSizeMake(75, 35));
            make.center.equalTo(self.view);
        }];
        
        ///按钮渐变色
        //CAGradientLayer继承CALayer,可以设置渐变图层
        CAGradientLayer *grandientLayer = [[CAGradientLayer alloc] init];
        grandientLayer.frame = CGRectMake(0, 0, 75.0, 35.0);
        [button.layer addSublayer:grandientLayer];
        [button.layer insertSublayer:grandientLayer atIndex:0];
        //设置渐变的方向 左上(0,0)  右下(1,1)
        grandientLayer.startPoint = CGPointZero;
        grandientLayer.endPoint = CGPointMake(1.0, 0.0);
        //colors渐变的颜色数组 这个数组中只设置一个颜色是不显示的
        grandientLayer.colors = @[(id)HEXCOLOR(0x5C9CDC).CGColor, (id)HEXCOLOR(0x657CDA).CGColor];
        grandientLayer.type = kCAGradientLayerAxial;
        
    }
    
    - (void)goBaiDu{
        BaseWebViewControlle *controller = [[BaseWebViewControlle alloc]init];
        controller.url = @"https://www.baidu.com";
        [self.navigationController pushViewController:controller animated:YES];
    }
    
    @end
    
    
    //
    //  BaseWebViewControlle.h
    
    #import <UIKit/UIKit.h>
    
    @interface BaseWebViewControlle : UIViewController
    
    @property (nonatomic, copy) NSString *url;
    
    @end
    
    
    //
    //  YSCBaseWebViewController.m
    
    
    #import "BaseWebViewControlle.h"
    #import <WebKit/WebKit.h>
    #import "BaseWebViewTitleView.h"
    #import "Masonry.h"
    
    @interface BaseWebViewControlle ()<WKNavigationDelegate,WKScriptMessageHandler,WKUIDelegate >
    
    
    @property (nonatomic, strong) WKWebView *webView;
    @property (nonatomic, strong) UIProgressView *progressView;
    //NSRegularExpression正则表达式专用类
    @property (nonatomic,strong) NSRegularExpression *goodsExpression;
    @property (nonatomic,strong) NSRegularExpression *loginExpression;
    @property (nonatomic,strong) NSRegularExpression *cartExpression;
    @end
    
    @implementation BaseWebViewControlle
    
    ///该方法只会在控制器加载完view时被调用,viewDidLoad通常不会被第二次调用除非这个view因为某些原因没有及时加载出来
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        
        [self setUpCommonHeader];
        
        self.view.backgroundColor = [UIColor whiteColor];
        //创建webview配置对象
        WKWebViewConfiguration *webConfig = [[WKWebViewConfiguration alloc] init];
        // 设置偏好设置
        webConfig.preferences = [[WKPreferences alloc] init];
        // 设置最小字体,默认值为0
        webConfig.preferences.minimumFontSize = 10;
        // 是否启用 javaScript,默认值为YES
        webConfig.preferences.javaScriptEnabled = YES;
        // 在iOS上默认为NO,表示不能自动通过窗口打开
        webConfig.preferences.javaScriptCanOpenWindowsAutomatically = NO;
        // web内容处理池
        webConfig.processPool = [[WKProcessPool alloc] init];
        // 将所有cookie以document.cookie = 'key=value';形式进行拼接
        
        //然而这里的单引号一定要注意是英文的
        //格式  @"document.cookie = 'key1=value1';document.cookie = 'key2=value2'";
        NSString *cookie = [self getCookie];
        
        //注入js修改返回按钮的点击事件
        NSString *scriptStr =  [NSString stringWithFormat:@"function backHomeClick_test(){window.webkit.messageHandlers.backHomeClick_test.postMessage(null);}(function(){document.getElementsByClassName('sb-back')[0].href = 'javascript:window.backHomeClick_test()';}());"];
        //WKUserScript 对象表示可以注入到网页中的脚本
        WKUserScript *userScript = [[WKUserScript alloc] initWithSource:scriptStr injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
        
        //内容交互控制器,自己注入JS代码及JS调用原生方法注册
        WKUserContentController* userContentController = WKUserContentController.new;
        //添加脚本消息处理程序
        [userContentController addScriptMessageHandler:self name:@"backHomeClick_test"];
        //加cookie给h5识别,表明在iOS端打开该地址
        WKUserScript * cookieScript = [[WKUserScript alloc]
                                       initWithSource: cookie
                                       injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
        //添加用户脚本
        [userContentController addUserScript:cookieScript];
        //添加用户脚本
        [userContentController addUserScript:userScript];
        
        webConfig.userContentController = userContentController;
        
        //初始化webView
        self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) configuration:webConfig];
        //添加webView
        [self.view addSubview:self.webView];
        //设置背景颜色
        self.webView.backgroundColor = [UIColor whiteColor];
        //设置代理
        self.webView.navigationDelegate = self;
        //设置代理
        self.webView.UIDelegate = self;
        //为webView添加观察者
        [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
        //添加脚本消息处理程序
        [[_webView configuration].userContentController addScriptMessageHandler:self name:@"historyGo"];
        //获取状态栏的Frame
        CGRect statusRect = [[UIApplication sharedApplication] statusBarFrame];
        //获取导航栏的Frame
        CGRect navigationRect = self.navigationController.navigationBar.frame;
        //设置内边距
        [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(statusRect.size.height + navigationRect.size.height ,0,0,0));
        }];
        //UIProgressView用户界面进度视图
        self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
        //进度条颜色
        self.progressView.progressTintColor = [UIColor purpleColor];
        //添加进度视图
        [self.webView addSubview:self.progressView];
        //设置约束
        [self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.left.right.equalTo(self.webView);
            make.height.mas_equalTo(2.0);
        }];
        //加载请求
        [self loadRequestWithUrlString:self.url];
        
    }
    
    ///该方法会在view要被显示出来之前被调用。这总是会发生在ViewDidload被调用之后并且每次view显示之前都会调用该方法。
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        [self.navigationController setNavigationBarHidden:NO animated:YES];
    }
    
    ///视图被销毁,此处需要对你在init和viewDidLoad中创建的对象进行释放
    - (void)dealloc {
        //移除观察者
        [self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
    }
    
    - (void)setUpCommonHeader {
        if (self.navigationController.viewControllers.count > 1 ||
            self.presentingViewController) {
            UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
            [backButton addTarget:self action:@selector(backToPreviousViewController) forControlEvents:UIControlEventTouchUpInside];
            backButton.frame = CGRectMake(0, 0, 25.0, 25.0);
            backButton.imageEdgeInsets = UIEdgeInsetsMake(0, -12, 0, 0);
            [backButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateNormal];
            [backButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateHighlighted];
            UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
            self.navigationItem.leftBarButtonItem = backBarButtonItem;
        }
    }
    
    ///返回上一个视图控制器
    - (void)backToPreviousViewController {
        [self goBack];
    }
    
    - (void)goBack {
        //如果可以返回
        if([self.webView canGoBack]) {
            //进行返回
            [self.webView goBack];
    
        }else{
            [self.navigationController popViewControllerAnimated:YES];
        }
    }
    
    ///加载请求
    - (void)loadRequestWithUrlString:(NSString *)urlString {
        // 在此处获取返回的cookie
        NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
        NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
        //NSHTTPCookieStorage,提供了管理所有NSHTTPCookie对象的接口,NSHTTPCookieStorage类采用单例的设计模式
        NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        
        for (NSHTTPCookie *cookie in [cookieJar cookies]) {
            [cookieDic setObject:cookie.value forKey:cookie.name];
        }
        // cookie重复,先放到字典进行去重,再进行拼接
        for (NSString *key in cookieDic) {
            NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
            [cookieValue appendString:appendString];
        }
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        [request addValue:cookieValue forHTTPHeaderField:@"Cookie"];
        //加载请求
        [self.webView loadRequest:request];
    }
    
    - (NSString*) getCookie {
        // 在此处获取返回的cookie
        NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
        NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
        NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        
        for (NSHTTPCookie *cookie in [cookieJar cookies]) {
            [cookieDic setObject:cookie.value forKey:cookie.name];
        }
        // cookie重复,先放到字典进行去重,再进行拼接
        for (NSString *key in cookieDic) {
            NSString *appendString = [NSString stringWithFormat:@"document.cookie = '%@=%@';", key, [cookieDic valueForKey:key]];
            [cookieValue appendString:appendString];
        }
        return cookieValue;
    }
    
    - (BOOL)canHandleUrl:(NSString *)url {
        if([self.loginExpression matchesInString:url options:0 range:NSMakeRange(0, url.length)].count > 0){
            return YES;
        } else if([self.goodsExpression matchesInString:url options:0 range:NSMakeRange(0, url.length)].count > 0){
            return YES;
        } else if([self.cartExpression matchesInString:url options:1 range:NSMakeRange(0, url.length)].count > 0) {
            
            return YES;
        }
        return NO;
    }
    
    ///删除空格和换行符
    - (NSString *)removeSpaceAndNewline:(NSString *)str {
        
        NSString *temp = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
        temp = [temp stringByReplacingOccurrencesOfString:@"\r" withString:@""];
        temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
        temp = [temp stringByReplacingOccurrencesOfString:@"\t" withString:@""];
        return temp;
    }
    
    /// 观察者方法,监听webView加载进度,调整进度条百分比
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:@"estimatedProgress"]) {
            //estimatedProgress当前导航的网页已经加载的估计值(double:0.0~1.0)
            [self.progressView setProgress:self.webView.estimatedProgress animated:YES];
            self.progressView.progress = self.webView.estimatedProgress;
            if (self.webView.estimatedProgress == 1.0) {
                [self.progressView removeFromSuperview];
            }
        }
    }
    
    #pragma make -- WKNavigationDelegate
    
    /// 在发送请求之前,决定是否允许或取消导航
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
        
        NSURL *URL = navigationAction.request.URL;
        
        NSString *scheme = [URL scheme];
        
        if ([self canHandleUrl:navigationAction.request.URL.absoluteString]) {
            
            decisionHandler(WKNavigationActionPolicyCancel);
            
        } else if ([scheme isEqualToString:@"tel"]) {
            
            NSString *resourceSpecifier = [URL resourceSpecifier];
            NSString *callPhone = [NSString stringWithFormat:@"telprompt:%@", resourceSpecifier];
            /// 防止iOS 10及其之后,拨打电话系统弹出框延迟出现
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callPhone] options:@{}
               completionHandler:^(BOOL success) {
                   NSLog(@"Open %@: %d",scheme,success);
               }];
            decisionHandler(WKNavigationActionPolicyAllow);
            
        } else {
            
            //如果是跳转一个新页面
            if (navigationAction.targetFrame == nil) {
                [webView loadRequest:navigationAction.request];
            }
            decisionHandler(WKNavigationActionPolicyAllow);
        }
    }
    
    /// 页面加载完成之后调用
    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
        
        NSString *tempString = [NSString stringWithFormat:@"document.getElementsByClassName('header-middle')[0].innerHTML"];
        [webView evaluateJavaScript:tempString completionHandler:^(id Result, NSError * _Nullable error) {
            if (!error) {
                NSString *title = [self removeSpaceAndNewline:Result];
                NSError *error = nil;
                //  判断字符串是否包含html标签,包含则设置标题为webView.title
                NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"^<(\\s+|\\S+)>$" options:NSRegularExpressionCaseInsensitive error:&error];
                NSArray *result = [regularExpression matchesInString:title options:NSMatchingReportProgress range:NSMakeRange(0, title.length)];
                if (result.count) {
                    [self setTitle:webView.title];
                } else {
                    [self setTitle:title];
                }
                
            } else {
                [self setTitle:webView.title];
            }
        }];
        
    }
    
    /// 页面开始加载web内容时调用
    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
        NSString *url = webView.URL.absoluteString;
        NSLog(@"%@",url);
    }
    
    /// 接收到服务器重定向之后调用
    - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
        NSLog(@"%@",webView.URL);
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:webView.URL];
        [self.webView loadRequest:request];
    }
    
    /// 在收到响应之后决定是否跳转
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
        
        NSLog(@"%@",navigationResponse.response.URL.absoluteString);
        //允许跳转
        decisionHandler(WKNavigationResponsePolicyAllow);
        //不允许跳转
        //decisionHandler(WKNavigationResponsePolicyCancel);
    }
    
    ///页面加载失败时调用 (web视图加载内容时发生错误)
    -(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error{
        //重定向要忽略一个-999的错误,代理检测到这个错误可能先执行该方法再去重定向
        if(error.code == -999){
            return;
        }
        NSLog(@"加载错误时候才调用,错误原因=%@",error);
        
    }
    
    /// web视图导航过程中发生错误时调用
    - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
        NSLog(@"%@", error);
    }
    
    #pragma make -- WKScriptMessageHandler
    
    ///当从网页接收到脚本消息时调用
    //OC在JS调用方法做的处理
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
    {
        if ([message.name isEqualToString:@"backHomeClick_test"]) {
            [self backToPreviousViewController];
        }
    }
    
    #pragma make -- WKUIDelegate
    
    ///显示一个 JavaScript 警告弹窗
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
        
    }
    
    ///显示一个 JavaScript 确认面板
    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
        
        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];
    }
    
    ///显示一个 JavaScript 文本输入面板
    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
            textField.text = defaultText;
        }];
        [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler(alertController.textFields[0].text?:@"");
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
    }
    
    @end
    
    

    运行:


    Untitled.gif

    关于加载WKWebView白屏问题的记录

    处理办法,转载自iOS__峰的博客:

    自ios8推出wkwebview以来,极大改善了网页加载速度及内存泄漏问题,逐渐全面取代笨重的UIWebview。尽管高性能、高刷新的WKWebview在混合开发中大放异彩表现优异,但加载网页过程中出现异常白屏的现象却仍然屡见不鲜,且现有的api协议处理捕捉不到这种异常case,造成用户无用等待体验很差。

    针对业务场景需求,实现加载白屏检测。考虑采用字节跳动团队提出的webview优化技术方案。在合适的加载时机对当前webview可视区域截图,并对此快照进行像素点遍历,如果非白屏颜色的像素点超过一定的阈值,认定其为非白屏,反之重新加载请求。

    IOS官方提供了简易的获取webview快照接口,通过异步回调拿到当前可视区域的屏幕截图。如下:

    - (void)takeSnapshotWithConfiguration:(nullable WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage * _Nullable snapshotImage, NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0));

    函数描述 : 获取WKWebView可见视口的快照。如果WKSnapshotConfiguration为nil,则该方法将快照WKWebView并创建一个图像,该图像是WKWebView边界的宽度,并缩放到设备规模。completionHandler被用来传递视口内容的图像或错误。

    参数 :

    snapshotConfiguration : 指定如何配置快照的对象

    completionHandler : 快照就绪时要调用的块。

    - (void)takeSnapshotWithConfiguration:(nullable WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage * _Nullable snapshotImage, NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0));
    

    其中snapshotConfiguration 参数可用于配置快照大小范围,默认截取当前客户端整个屏幕区域。由于可能出现导航栏成功加载而内容页却空白的特殊情况,导致非白屏像素点数增加对最终判定结果造成影响,考虑将其剔除。如下:

    - (void)judgeLoadingStatus:(WKWebView *)webview {
        if (@available(iOS 11.0, *)) {
            if (webView && [webView isKindOfClass:[WKWebView class]]) {
     
                CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //状态栏高度
                CGFloat navigationHeight =  webView.viewController.navigationController.navigationBar.frame.size.height; //导航栏高度
                WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
                shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, _webView.bounds.size.width, (_webView.bounds.size.height - navigationHeight - statusBarHeight)); //仅截图检测导航栏以下部分内容
                [_webView takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                    //todo
                }];
            }
        }
    }
    

    缩放快照,为了提升检测性能,考虑将快照缩放至1/5,减少像素点总数,从而加快遍历速度。如下 :

    - (UIImage *)scaleImage: (UIImage *)image {
        CGFloat scale = 0.2;
        CGSize newsize;
        newsize.width = floor(image.size.width * scale);
        newsize.height = floor(image.size.height * scale);
        if (@available(iOS 10.0, *)) {
            UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
              return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                            [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                     }];
        }else{
            return image;
        }
    }  
    

    缩小前后性能对比(实验环境:iPhone11同一页面下):

    缩放前白屏检测:

    image image

    耗时20ms。

    缩放后白屏检测:

    image image

    耗时13ms。

    注意这里有个小坑。由于缩略图的尺寸在 原图宽高*缩放系数后可能不是整数,在布置画布重绘时默认向上取整,这就造成画布比实际缩略图大(混蛋啊 摔!)。在遍历缩略图像素时,会将图外画布上的像素纳入考虑范围,导致实际白屏页 像素占比并非100% 如图所示。因此使用floor将其尺寸大小向下取整。

    遍历快照缩略图像素点,对白色像素(R:255 G: 255 B: 255)占比大于95%的页面,认定其为白屏。如下

    - (BOOL)searchEveryPixel:(UIImage *)image {
        CGImageRef cgImage = [image CGImage];
        size_t width = CGImageGetWidth(cgImage);
        size_t height = CGImageGetHeight(cgImage);
        size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //每个像素点包含r g b a 四个字节
        size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
     
        CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
        CFDataRef data = CGDataProviderCopyData(dataProvider);
        UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);
     
        int whiteCount = 0;
        int totalCount = 0;
     
        for (int j = 0; j < height; j ++ ) {
            for (int i = 0; i < width; i ++) {
                UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
                UInt8 red   = * pt;
                UInt8 green = *(pt + 1);
                UInt8 blue  = *(pt + 2);
    //            UInt8 alpha = *(pt + 3);
     
                totalCount ++;
                if (red == 255 && green == 255 && blue == 255) {
                    whiteCount ++;
                }
            }
        }
        float proportion = (float)whiteCount / totalCount ;
        NSLog(@"当前像素点数:%d,白色像素点数:%d , 占比: %f",totalCount , whiteCount , proportion );
        if (proportion > 0.95) {
            return YES;
        }else{
            return NO;
        }
    } 
    

    总结:

    typedef NS_ENUM(NSUInteger,webviewLoadingStatus) {
     
        WebViewNormalStatus = 0, //正常
     
        WebViewErrorStatus, //白屏
     
        WebViewPendStatus, //待决
    };
    
    /// 判断是否白屏
    - (void)judgeLoadingStatus:(WKWebView *)webview  withBlock:(void (^)(webviewLoadingStatus status))completionBlock{
        webviewLoadingStatus __block status = WebViewPendStatus;
        if (@available(iOS 11.0, *)) {
            if (webview && [webview isKindOfClass:[WKWebView class]]) {
     
                CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //状态栏高度
                CGFloat navigationHeight = self.navigationController.navigationBar.frame.size.height; //导航栏高度
                WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
                shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, webview.bounds.size.width, (webview.bounds.size.height - navigationHeight - statusBarHeight)); //仅截图检测导航栏以下部分内容
                [webview takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                    if (snapshotImage) {
                        UIImage * scaleImage = [self scaleImage:snapshotImage];
                        BOOL isWhiteScreen = [self searchEveryPixel:scaleImage];
                        if (isWhiteScreen) {
                           status = WebViewErrorStatus;
                        }else{
                           status = WebViewNormalStatus;
                        }
                    }
                    if (completionBlock) {
                        completionBlock(status);
                    }
                }];
            }
        }
    }
     
    /// 遍历像素点 白色像素占比大于95%认定为白屏
    - (BOOL)searchEveryPixel:(UIImage *)image {
        CGImageRef cgImage = [image CGImage];
        size_t width = CGImageGetWidth(cgImage);
        size_t height = CGImageGetHeight(cgImage);
        size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //每个像素点包含r g b a 四个字节
        size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
     
        CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
        CFDataRef data = CGDataProviderCopyData(dataProvider);
        UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);
     
        int whiteCount = 0;
        int totalCount = 0;
     
        for (int j = 0; j < height; j ++ ) {
            for (int i = 0; i < width; i ++) {
                UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
                UInt8 red   = * pt;
                UInt8 green = *(pt + 1);
                UInt8 blue  = *(pt + 2);
     
                totalCount ++;
                if (red == 255 && green == 255 && blue == 255) {
                    whiteCount ++;
                }
            }
        }
        float proportion = (float)whiteCount / totalCount ;
        NSLog(@"当前像素点数:%d,白色像素点数:%d , 占比: %f",totalCount , whiteCount , proportion );
        if (proportion > 0.95) {
            return YES;
        }else{
            return NO;
        }
    }
     
    ///缩放图片
    - (UIImage *)scaleImage: (UIImage *)image {
        CGFloat scale = 0.2;
        CGSize newsize;
        newsize.width = floor(image.size.width * scale);
        newsize.height = floor(image.size.height * scale);
        if (@available(iOS 10.0, *)) {
            UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
              return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                            [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                     }];
        }else{
            return image;
        }
    }
    
    

    在页面加载完成的代理函数中进行判断,决定是否重新进行加载,如下:

    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
        [self judgeLoadingStatus:webView withBlock:^(webviewLoadingStatus status) {
            if(status == WebViewNormalStatus){
                //页面状态正常
                [self stopIndicatorWithImmediate:NO afterDelay:1.5f indicatorString:@"页面加载完成" complete:nil];
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [self.webView evaluateJavaScript:@"signjsResult()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
                    }];
                });
            }else{
                //可能发生了白屏,刷新页面
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    NSURLRequest *request = [[NSURLRequest alloc]initWithURL:webView.URL];
                    [self.webView loadRequest:request];
                });
            }
        }];
    }
    

    经过测试,发现页面加载白屏时函数确实可以检测到。经过排查,发现造成偶性白屏时,控制台输出内容如下 : urface creation failed for size。是由于布局问题产生了页面白屏,白屏前设置约束的代码如下 :

    - (void)viewDidLoad {
        [super viewDidLoad];
        //设置控制器标题
        self.title = self.model.menuName;
        //设置服务器URL字符串
        NSString *str;
        str =[NSString stringWithFormat:@"%@%@?access_token=%@",web_base_url,self.model.request,access_token_macro];
        NSLog(@"======:%@",str);
        str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
        //设置web视图属性
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
        WKPreferences *preference = [[WKPreferences alloc]init];
        configuration.preferences = preference;
        configuration.selectionGranularity = YES; //允许与网页交互
        //设置web视图
        self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
        self.webView.navigationDelegate = self;
        self.webView.UIDelegate = self;
        [self.webView.scrollView setShowsVerticalScrollIndicator:YES];
        [self.webView.scrollView setShowsHorizontalScrollIndicator:YES];
           [self.view addSubview:self.webView];
        [[self.webView configuration].userContentController addScriptMessageHandler:self name:@"signjs"];
        /* 加载服务器url的方法*/
        NSString *url = str;
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20];
        [self.webView loadRequest:request];
        
    }
    
    ///设置视图约束
    - (void)updateViewConstraints {
        [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
            if (@available(iOS 11.0, *)) {
                make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
            } else {
                make.top.equalTo(self.view.mas_topMargin);
            }
            make.left.bottom.right.equalTo(self.view);
        }];
        [super updateViewConstraints];
    }
    

    改为再将WKWebView视图添加到控制器视图后,直接设置约束,偶发性白屏问题消失,由于布局问题引发的白屏,即使重新加载页面也不会解决的,会陷入死循环当中。如下 :

    - (void)viewDidLoad {
        [super viewDidLoad];
        //设置控制器标题
        self.title = self.model.menuName;
        //设置服务器URL字符串
        NSString *str;
        str =[NSString stringWithFormat:@"%@%@?access_token=%@",web_base_url,self.model.request,access_token_macro];
        NSLog(@"======:%@",str);
        str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
        //设置web视图属性
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
        WKPreferences *preference = [[WKPreferences alloc]init];
        configuration.preferences = preference;
        configuration.selectionGranularity = YES; //允许与网页交互
        //设置web视图
        self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
        self.webView.navigationDelegate = self;
        self.webView.UIDelegate = self;
        [self.webView.scrollView setShowsVerticalScrollIndicator:YES];
        [self.webView.scrollView setShowsHorizontalScrollIndicator:YES];
        [[self.webView configuration].userContentController addScriptMessageHandler:self name:@"signjs"];
        [self.view addSubview:self.webView];
        [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
            if (@available(iOS 11.0, *)) {
                make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
            } else {
                make.top.equalTo(self.view.mas_topMargin);
            }
            make.left.bottom.right.equalTo(self.view);
        }];
        /* 加载服务器url的方法*/
        NSString *url = str;
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20];
        [self.webView loadRequest:request];
        
    }
    

    相关文章

      网友评论

          本文标题:WKWebView学习笔记

          本文链接:https://www.haomeiwen.com/subject/sjkxlctx.html