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.gifUIProgressView
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];
}
网友评论