在开发中经常会遇到iOS与HTML混编的情况,iOS加载HTML可以使用UIWebView和WKWebView.本文先来介绍这两种方案中的UIWebView.
一,UIWebView简介
UIWebView的属性:
// 代理属性 重点需要知道代理方法的使用
@property (nullable, nonatomic, assign) id delegate;
// 这个是webView内部的scrollView 只读,但是利用这个属性,设置scrollView的代理,就可以控制整个webView的滚动事件
@property(nonatomic, readonly, strong) UIScrollView *scrollView;
// webView的请求,这个属性一般在整个加载完成后才能拿到
@property (nullable, nonatomic, readonly, strong) NSURLRequest *request;
// A Boolean value indicating whether the receiver can move backward. (read-only)
// If YES, able to move backward; otherwise, NO.
// 如果这个属性为YES,才能后退
@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack;
// A Boolean value indicating whether the receiver can move forward. (read-only)
// If YES, able to move forward; otherwise, NO.
// 如果这个属性为YES,才能前进
@property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward;
// A Boolean value indicating whether the receiver is done loading content. (read-only)
// If YES, the receiver is still loading content; otherwise, NO.
// 这个属性很好用,如果为YES证明webView还在加载数据,所有数据加载完毕后,webView就会为No
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
//A Boolean value determining whether the webpage scales to fit the view and the user can change the scale.
//If YES, the webpage is scaled to fit and the user can zoom in and zoom out. If NO, user zooming is disabled. The default value is NO.
// YES代表网页可以缩放,NO代表不可以缩放
@property (nonatomic) BOOL scalesPageToFit;
// 设置某些数据变为链接形式,这个枚举可以设置如电话号,地址,邮箱等转化为链接
@property (nonatomic) UIDataDetectorTypes dataDetectorTypes NS_AVAILABLE_IOS(3_0);
// iPhone Safari defaults to NO. iPad Safari defaults to YES
// 设置是否使用内联播放器播放视频
@property (nonatomic) BOOL allowsInlineMediaPlayback NS_AVAILABLE_IOS(4_0);
// iPhone and iPad Safari both default to YES
// 设置视频是否自动播放
@property (nonatomic) BOOL mediaPlaybackRequiresUserAction NS_AVAILABLE_IOS(4_0);
// iPhone and iPad Safari both default to YES
// 设置音频播放是否支持ari play功能
@property (nonatomic) BOOL mediaPlaybackAllowsAirPlay NS_AVAILABLE_IOS(5_0);
// iPhone and iPad Safari both default to NO
// 设置是否将数据加载入内存后渲染界面
@property (nonatomic) BOOL suppressesIncrementalRendering NS_AVAILABLE_IOS(6_0);
// default is YES
// 设置用户是否能打开keyboard交互
@property (nonatomic) BOOL keyboardDisplayRequiresUserAction NS_AVAILABLE_IOS(6_0);
/* IOS7 */ 以后的新特性
// 这个属性用来设置一种模式,当网页的大小超出view时,将网页以翻页的效果展示,枚举如下:
@property (nonatomic) UIWebPaginationMode paginationMode NS_AVAILABLE_IOS(7_0);
typedef NS_ENUM(NSInteger, UIWebPaginationMode) {
UIWebPaginationModeUnpaginated, //不使用翻页效果
UIWebPaginationModeLeftToRight, //将网页超出部分分页,从左向右进行翻页
UIWebPaginationModeTopToBottom, //将网页超出部分分页,从上向下进行翻页
UIWebPaginationModeBottomToTop, //将网页超出部分分页,从下向上进行翻页
UIWebPaginationModeRightToLeft //将网页超出部分分页,从右向左进行翻页
};
// This property determines whether certain CSS properties regarding column- and page-breaking are honored or ignored.
// 这个属性决定CSS的属性分页是可用还是忽略。默认是UIWebPaginationBreakingModePage
@property (nonatomic) UIWebPaginationBreakingMode paginationBreakingMode NS_AVAILABLE_IOS(7_0);
// 设置每一页的长度
@property (nonatomic) CGFloat pageLength NS_AVAILABLE_IOS(7_0);
// 设置每一页的间距
@property (nonatomic) CGFloat gapBetweenPages NS_AVAILABLE_IOS(7_0);
// 获取页数
@property (nonatomic, readonly) NSUInteger pageCount NS_AVAILABLE_IOS(7_0);
UIWebView的代理方法:
// Sent before a web view begins loading a frame.请求发送前都会调用该方法,返回NO则不处理这个请求
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
// Sent after a web view starts loading a frame. 请求发送之后开始接收响应之前会调用这个方法
- (void)webViewDidStartLoad:(UIWebView *)webView;
// Sent after a web view finishes loading a frame. 请求发送之后,并且服务器已经返回响应之后调用该方法
- (void)webViewDidFinishLoad:(UIWebView *)webView;
// Sent if a web view failed to load a frame. 网页请求失败则会调用该方法
- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;
UIWebView的对象方法:
// 加载Data数据创建一个webView
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL
// 加载本地HTML创建一个webView
- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL
// 加载一个请求创建一个webView
- (void)loadRequest:(NSURLRequest *)request
// 刷新网页
- (void)reload;
// 停止网页加载内容
- (void)stopLoading;
// 后退
- (void)goBack;
// 前进
- (void)goForward;
// 执行JS方法
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script
二,UIWebView自带方法交互
以下所有代码demo:点击获取
OC调用js
[self.webView stringByEvaluatingJavaScriptFromString:@"cli()"];
// 获取当前页面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];
// 获取当前页面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];
js调用OC(OC代码)
/**使用UIWebView自带方法进行js调用OC的时候,在这个方法中讲oc方法注入js*/
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
/**获取点击页面加载的url,此url为前端与移动端约定好的,比如这个demo,约定好的请求头就是'demo',然后在'://后面添加不同的字段来判断不同的事件,可以看indexDemo.html中的js写法'*/
NSString *requestString = request.URL.absoluteString;
/**以'://将URL分割'*/
NSArray *tmpArr = [requestString componentsSeparatedByString:@"://"];
if ([tmpArr.firstObject isEqualToString:@"demo"]) {
/**通过不同的字段来进行不同的事件处理*/
if ([tmpArr.lastObject isEqualToString:@"goSleep"]) {
[self AlertWithStr:@"去睡觉吧"];
} else if ([tmpArr.lastObject isEqualToString:@"goEat"]) {
[self AlertWithStr:@"去吃饭吧"];
} else if ([tmpArr.lastObject isEqualToString:@"goDrink"]) {
[self AlertWithStr:@"去喝水吧"];
}
return NO;
}
return YES;
}
js调用OC(js代码)
var btn=function (id) {
if (id == "sleep") {
location.href = "demo://goSleep"
} else if (id == "water") {
location.href = "demo://goDrink"
} else if (id == "food") {
location.href = "demo://goEat"
}
}
三,使用javascriptCore进行交互
JavaScriptCore 简介
iOS7 中新加入的 JavaScriptCore.framework 可能被大多数开发人员所忽略,但是如果你之前就在项目中用过自己编译JavaScriptCore来处理 JavaScript,那么你需要重新关注一下 JavaScriptCore.framework。
JavaScriptCore 是苹果 Safari 浏览器的 JavaScript 引擎,或许你之前听过 Google 的 V8 引擎,在 WWDC 上苹果演示了最新的 Safari,据说 JavaScript 处理速度已经大大超越了 Google 的 Chrome,这就意味着 JavaScriptCore 在性能上也不输 V8 了。
其实 JavaScriptCore.framework 在 OS X 平台上很早就存在的,不过接口都是纯 C 语言的,而在 iOS 平台,苹果没有开放该 framework,所以不少需要在 iOS app 中处理 JavaScript 的都得自己从开源的 WebKit 中编译出 JavaScriptCore.a,接口也是纯 C 语言的。可能是苹果发现越来越多的程序使用了自编译的 JavaScriptCore,干脆做个顺水人情将 JavaScriptCore.framework 开放了,同时还提供了 Objective-C 的接口。
JavaScriptCore和我们相关的类只有五个:
JSContext
JS执行的环境,同时也通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。
JSValue
JS对象在JSVirtualMachine中的一个强引用,其实就是Hybird对象。我们对JS的操作都是通过它。并且每个JSValue都是强引用一个context。同时,OC和JS对象之间的转换也是通过它.
JSManagedValue
JS和OC对象的内存管理辅助对象。由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。
JSVirtualMachine
JS运行的虚拟机,有独立的堆空间和垃圾回收机制。
JSExport
一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。
代码示例
OC调用js(OC代码)
/**获取上下文,固定写法*/
JSContext *context=[self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *tmp = @"inputtext('哈哈哈哈哈哈')";
[context evaluateScript:tmp];
OC调用js(js代码)
function inputtext (text) {
document.querySelector('.test').innerHTML = text;
}
1,block方式
block也可以称作闭包和匿名函数,使用block可以很方便的将OC中的单个方法暴露给JS调用,JSCore会自动将这个Block包装成一个JS方法.
js调用OC(OC代码)
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
__weak JSCoreViewController *weakSelf = self;
context[@"go"] = ^(id str){
/**不要在block内部直接使用JSContext和JSValue,因为Block会强引用它里面用到的外部变量,如果直接在Block中使用JSValue的话,那么这个JSvalue就会被这个Block强引用,而每个JSValue都是强引用着它所属的那个JSContext的,而这个Block又是注入到这个Context中,所以这个Block会被context强引用,这样会造成循环引用,导致内存泄露。不能直接使用JSContext的原因同理。正确的驶入方法如下:*/
NSArray *tmp = [JSContext currentArguments];
for (JSValue *m in tmp) {
NSString *mm = m.toString;
if ([mm isEqualToString:@"water"]) {
[weakSelf AlertWithStr:@"去喝水"];
} else if ([mm isEqualToString:@"sleep"]) {
[weakSelf AlertWithStr:@"去睡觉"];
} else if ([mm isEqualToString:@"food"]) {
[weakSelf AlertWithStr:@"去吃饭"];
}
}
};
}
js调用OC(js代码)
var btn=function (id) {
go(id);
}
2,JSExport方式
通过JSExport 协议可以很方便的将OC中的对象暴露给JS使用,且在JS中用起来就和JS对象一样。
自定义model.h
@protocol DemoModelProtocol
/**这个方法得命名需要注意,
OC的方法名字应该与js名字统一,
OC:- (void)name:(NSString *)name Something:(NSString *)something;
js:
OC:- (void)test;
js:
*/
- (void)name:(NSString *)name Something:(NSString *)something;
- (void)test;
@end
@interface DemoModel : NSObject
/**VC*/
@property (nonatomic, weak)UIViewController *VC;
@end
自定义model.m
@implementation DemoModel
- (void)name:(NSString *)name Something:(NSString *)something
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:name message:something preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addAction:action];
[_VC presentViewController:alert animated:YES completion:nil];
}
- (void)test
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"试试" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addAction:action];
[_VC presentViewController:alert animated:YES completion:nil];
}
@end
js调用OC(OC代码)
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
DemoModel *model = [[DemoModel alloc] init];
model.VC = self;
context[@"test"] = model;
}
js调用OC(js代码)
三,使用WebViewJavascriptBridge进行交互
初始化webView
UIWebView *webView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.view addSubview:webView];
[WebViewJavascriptBridge enableLogging];
self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
[self.bridge setWebViewDelegate:self];
NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewJavascriptBridgeDemo" ofType:@"html"];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
[self jsCalliOS];
这段代码是固定的,必须要放到js中
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
setupWebViewJavascriptBridge(function(bridge) {
/* 所有与iOS交互的JS代码放这里!*/
})
OC调用js(OC代码)
[self.bridge callHandler:@"changeHH" data:@"你好,我是胡杨" responseCallback:^(id responseData) {
NSLog(@"%@",responseData);
}];
OC调用js(js代码)
/**修改hhhh*/
bridge.registerHandler('changeHH', function(data, responseCallback) {
//处理oc给的传参
inputtext(data)
var responseData = { 'result':'handle success' }
//处理完,回调传值给oc
responseCallback(responseData)
})
js调用OC(OC代码)
- (void)jsCalliOS
{
[self.bridge registerHandler:@"go" handler:^(id data, WVJBResponseCallback responseCallback) {
if ([data[@"id"] isEqualToString:@"sleep"]) {
[self AlertWithStr:@"去睡觉" responseCallback:responseCallback];
} else if ([data[@"id"] isEqualToString:@"food"]) {
[self AlertWithStr:@"去吃饭" responseCallback:responseCallback];
} else if ([data[@"id"] isEqualToString:@"water"]) {
[self AlertWithStr:@"去喝水" responseCallback:responseCallback];
}
}];
}
js调用OC(js代码)
btn = function(id) {
bridge.callHandler('go', {'id':id}, function(response) {
//处理oc过来的回调
inputtext(response)
})
}
本文demo:点击获取
参考和相关文章:
https://blog.csdn.net/qq_21051503/article/details/78199440
https://www.cnblogs.com/yajunLi/p/6369257.html
深度交互(点击图片放大):
https://www.jianshu.com/p/e6a01c74f026
https://blog.csdn.net/longzs/article/details/50595616
https://www.jianshu.com/p/a329cd4a67ee
http://www.cocoachina.com/ios/20170720/19958.html