Objective-C的WKWebView学习笔记

WKWebView - Web视图

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视图中显示时的规模。此后,用户可以使用手势改变比例。

WKWebView常用属性
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;

属性描述 :只读属性,用于获取包含Web视图相关配置详细信息的对象。此属性返回的是配置对象的副本,所以对该对象所做的更改不会影响Web视图的配置。如果没有使用 initWithFrame:configuration: 方法创建Web视图,则此属性包含一个默认配置对象。

@property (nullable, nonatomic, weak) id  navigationDelegate;

属性描述对Web视图进行导航跟踪、身份验证等操作的代理对象,指定的对象必须符合 WKNavigationDelegate协议。

@property (nullable, nonatomic, weak) id  UIDelegate;

属性描述Web视图与IOS用户界面交互代理对象,如点击Web视图显示一个IOS的Alert警告弹窗需要此代理。

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

属性描述 :只读属性,Web视图的前向列表

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

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

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

属性描述 :只读属性,Web视图当前显示网页的URL。这个属性是兼容WKWebView类的键值观察(KVO)的。

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

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

@property (nonatomic, readonly) double estimatedProgress;

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

//为webView添加观察者
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];

//观察者方法,监听webView加载进度,调整进度条百分比
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)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 canGoBack;

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

- (void)goBack {
    //如果可以返回
    if([self.webView canGoBack]) {
        //进行返回
        [self.webView goBack];

    }else{
        //弹出当前视图控制器
        [self.navigationController popViewControllerAnimated:YES];
    }
}
@property (nonatomic, readonly) BOOL canGoForward;

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

@property (nonatomic) BOOL allowsBackForwardNavigationGestures;

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

WKWebView常用函数
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

函数描述此方法是WKWebView指定初始化方法,返回用指定的框架矩形和配置初始化的Web视图。接受指定初始值设定项的自定义WKWebViewConfiguration对象,Web视图会复制指定的配置并将复制的WKWebViewConfiguration对象赋值给configuration属性,所以初始化Web视图后更改配置对象对Web视图没有影响。要使用默认配置值创建Web视图,则调用继承的initWithFrame: 方法。

参数 :

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

configuration : 新Web视图的配置。

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

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

函数描述Web视图导航到请求的URL

参数 :

request : 指定要显示的资源的URL请求。

返回值:一个新的导航对象,用于跟踪请求的加载进度。

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

函数描述 :加载指定HTML字符串的内容并导航到它。使用此方法可以导航到程序中加载或创建的网页。例如,可以使用此方法加载应用程序中以编程方式生成的HTML内容。

参数 :

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

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

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

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

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

极其简单的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 : 脚本执行完成或失败时要调用的块。

WKWebViewConfiguration - Web视图相关配置

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

//初始化一个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];
WKWebViewConfiguration常用属性
@property (nonatomic, strong) WKProcessPool *processPool;

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

@property (nonatomic, strong) WKPreferences *preferences;

属性描述加载Web视图要使用的偏好设置,可以通过将新的WKPreferences对象分配给此属性来更改偏好设置。

@property (nonatomic, strong) WKUserContentController *userContentController;

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

@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 (nonatomic) BOOL allowsAirPlayForMediaPlayback;

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

@property (nonatomic) WKAudiovisualMediaTypes mediaTypesRequiringUserActionForPlayback;

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

  • WKAudiovisualMediaTypes枚举值如下:
typedef NS_OPTIONS(NSUInteger, WKAudiovisualMediaTypes) {
    //没有媒体类型需要用户手势才能开始播放
    WKAudiovisualMediaTypeNone = 0,
    //包含音频的媒体类型需要用户手势才能开始播放
    WKAudiovisualMediaTypeAudio = 1 << 0,
    //包含视频的媒体类型需要用户手势才能开始播放
    WKAudiovisualMediaTypeVideo = 1 << 1,
    //所有媒体类型都需要用户手势才能开始播放
    WKAudiovisualMediaTypeAll = NSUIntegerMax
}
@property (nonatomic) BOOL allowsInlineMediaPlayback;

属性描述 :一个布尔值,设置HTML5视频是否允许网页内联播放 ,设置为'NO'则会使用本地播放器,默认值是'NO'。在iPhone上将视频元素添加到HTML文档时,还必须包含playinline属性。

@property (nonatomic) WKSelectionGranularity selectionGranularity;

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

  • WKSelectionGranularity枚举值如下:
typedef NS_ENUM(NSInteger, WKSelectionGranularity) {
    //根据选择自动变化的粒度  
    WKSelectionGranularityDynamic,
    //允许用户将选择端点放置在任何字符边界的粒度
    WKSelectionGranularityCharacter,
}
@property (nonatomic) BOOL allowsPictureInPictureMediaPlayback;

属性描述 :一个布尔值,指示HTML5视频是否可以播放画中画,此属性的默认值为 YES。

@property (nonatomic) WKDataDetectorTypes dataDetectorTypes;

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

@property (nonatomic) BOOL suppressesIncrementalRendering;

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

@property (nonatomic) BOOL ignoresViewportScaleLimits;

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

@property (nonatomic) WKUserInterfaceDirectionPolicy userInterfaceDirectionPolicy;

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

//WKUserInterfaceDirectionPolicy枚举值如下:
typedef NS_ENUM(NSInteger, WKUserInterfaceDirectionPolicy) {
    //用户界面方向性遵循 CSS / HTML / XHTML规格
    WKUserInterfaceDirectionPolicyContent,
    //用户界面方向性遵循视图的userInterfaceLayoutDirection 属性
    WKUserInterfaceDirectionPolicySystem,
} 

WKPreferences - 加载Web视图的偏好设置

一个对象,用于封装要应用于加载Web视图要使用的偏好设置,包括最小字体大小、JavaScript行为和处理欺诈网站的行为。创建此对象,并将其分配给用于创建web视图的WKWebViewConfiguration对象的首选项属性。

WKPreferences常用属性
@property (nonatomic) CGFloat minimumFontSize;

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

@property (nonatomic) BOOL javaScriptEnabled;

属性描述 :一个布尔值,指示是否启用 JavaScript,将此属性设置为NO会禁用网页加载或执行的 JavaScript,此设置不影响用户脚本。默认值为YES。

@property (nonatomic) BOOL javaScriptCanOpenWindowsAutomatically;

属性描述 :一个布尔值,指示JavaScript是否可以在没有用户交互的情况下打开窗口,默认值为NO。

@property (nonatomic) BOOL javaEnabled;

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

@property (nonatomic) BOOL plugInsEnabled;

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

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

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

WKUserScript - 注入网页的脚本

Web视图注入网页的脚本。当想将自定义脚本代码注入Web视图的页面时,创建一个WKUserScript对象,使用此对象指定要注入的JavaScript代码,以及指定在什么时机怎样去注入这些JavaScript代码的相关参数。在创建Web视图之前,将此对象添加到与Web视图配置对象(WKWebViewConfiguration的userContentController属性)关联的WKUserContentController对象。

WKUserScript常用函数
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;

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

参数 :

source :脚本源。

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

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

WKUserScript常用属性
@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, getter=isForMainFrameOnly) BOOL;

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

WKUserContentController - Web视图与JavaScript代码的桥梁

一个对象,用于管理JavaScript代码和Web视图之间的交互,以及过滤Web视图中的内容。WKUserContentController对象在应用程序和运行在Web视图中的JavaScript代码之间提供了一座桥梁,使用该对象可完成以下任务:

  • 将JavaScript代码注入到Web视图运行的网页中。

  • 注入可以调用应用原生代码的自定义JavaScript函数。

  • 指定自定义过滤器用来防止网页加载受限制的内容。

创建并配置一个WKUserContentController对象,作为整个Web视图设置的一部分。在创建Web视图之前,将该对象分配给WKWebViewConfiguration对象的userContentController属性。

WKUserContentController常用属性
@property (nonatomic, readonly, copy) NSArray *userScripts;

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

WKUserContentController常用函数
- (void)addUserScript:(WKUserScript *)userScript;

函数描述将指定的脚本注入到网页的内容中

参数 :

userScript : 添加到Web视图的当前页面的用户脚本。

- (void)removeAllUserScripts;

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

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

函数描述注册要被JavaScript调用的方法名称,添加scriptMessageHandler将为当前网页内容的内容领域中添加一个函数window.webkit.messagehandlers..postmessage(),即在WKContentWorld的pageWorld属性的内容领域中。

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

在应用程序代理对象的-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message回调方法中,会获取到JavaScript传递进来的消息。

参数 :

scriptMessageHandler : 可以处理JavaScript所发送消息的对象,该对象必须采用WKScriptMessageHandler协议。

name :要被JavaScript调用的方法名称,此参数在WKUserContentController对象中必须是唯一的,并且不得为空字符串。

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

函数描述在JavaScript代码中移除之前使用addScriptMessageHandler:name: 方法注入的JavaScript可发送消息的函数。此方法从当前网页内容的内容领域中移除函数,即从WKContentWorld的pageWorld属性中获得的内容领域,如果将函数注入在其他内容领域中,则此方法不会将其删除。

参数 :

name : 要在JavaScript代码中移除的方法名称,如果移除的方法名称不存在则此方法不执行任何操作。

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

函数描述将指定的内容规则列表添加到WKUserContentController对象,调用此方法将一组内容筛选规则应用于Web视图的配置。

参数 :

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

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

函数描述从WKUserContentController对象中删除指定的规则列表,此方法仅从WKUserContentController对象中删除规则列表,但仍然可以从用于创建规则列表的WKContentRuleListStore对象访问规则列表。

参数 :

contentRuleList : 要删除的规则列表。

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

函数描述从WKUserContentController对象中删除所有规则列表,此方法仅从WKUserContentController对象中删除规则列表,但仍然可以从用于创建规则列表的WKContentRuleListStore对象访问规则列表。

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



    
        
        
        
        
        
        
    
    
        

第一次练习

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

#import "TestWebViewAndJSInteractiveController.h"

@interface TestWebViewAndJSInteractiveController ()

@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

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

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




    
    
        
        
        
        
        
        
    
    
    
        

第一次练习

以及再次进阶的IOS文件:

#import "TestWebViewAndJSInteractiveController.h"

@interface TestWebViewAndJSInteractiveController ()

@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类提供了一些属性,用于管理进度条的样式,以及获取和设置与任务进度相关的值。

UIProgressView初始化函数
- (instancetype)initWithProgressViewStyle:(UIProgressViewStyle)style;

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

  • UIProgressViewStyle枚举值如下:
typedef NS_ENUM(NSInteger, UIProgressViewStyle) {
    UIProgressViewStyleDefault,     // 正常的进度条
    UIProgressViewStyleBar __TVOS_PROHIBITED,     // 用于工具栏
};
UIProgressView常用属性
@property(nonatomic) float progress;

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

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

属性描述设置进度条颜色

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

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

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

属性描述设置进度条图片

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

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

WKNavigationDelegate - Web视图导航代理

WKNavigationDelegate 协议方法可以帮助你实现在Web视图接受,加载和完成导航请求的过程中触发的自定义行为。当用户尝试导航Web内容时,Web视图与其导航代理协调以管理任何转换。例如,可以使用这些方法来限制从内容中的特定链接进行导航。还可以使用它们来跟踪请求的进度,并对错误和身份验证挑战做出响应。

WKNavigationDelegate常用函数
- (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 didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

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

参数 :

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

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

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

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

参数 :

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

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

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

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

参数 :

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

navigation : 完成的导航对象。

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

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

参数 :

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

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

error : 发生的错误。

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

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

参数 :

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

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

error : 发生的错误。

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

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

参数 :

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

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

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

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

参数 :

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

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

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

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

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

参数 :

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

- (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 *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 - 处理JavaScript消息

符合WKScriptMessageHandler协议的类提供了一种方法,用于从网页中运行的JavaScript中接收消息。当应用程序需要在web视图中响应JavaScript消息时,需要采用WKScriptMessageHandler协议。当JavaScript代码发送专门针对消息处理程序的消息时,WebKit会调用处理程序的userContentController:didReceiveScriptMessage:方法,使用该方法来实现响应处理。

WKScriptMessageHandler常用函数
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

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

参数 :

userContentController : 调用委托方法的WKUserContentController

message : 收到的脚本消息

WKUIDelegate - 代表网页呈现本机用户界面

WKUIDelegate 类是网页视图的用户界面委托协议,提供了代表网页呈现本机用户界面元素的方法。Web视图用户界面代理实现该协议以控制新窗口的打开,增强用户单击元素时显示的默认菜单项的行为,并执行其他与用户界面相关的任务。这些方法可以作为处理JavaScript或其他插件内容的结果来调用。默认的Web视图实现假设每个Web视图有一个窗口,因此非传统的用户界面可能会实现用户界面代理。

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{
    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))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:(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 

@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 

@interface BaseWebViewControlle : UIViewController

@property (nonatomic, copy) NSString *url;

@end

//
//  BaseWebViewController.m


#import "BaseWebViewControlle.h"
#import 
#import "BaseWebViewTitleView.h"
#import "Masonry.h"

@interface BaseWebViewControlle ()


@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 *)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 : 快照就绪时要调用的块。

其中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];
    
}

你可能感兴趣的:(Objective-C的WKWebView学习笔记)