iOS中UIWebview、JavaScript与OC交互、Cookie管理

注,本文转载自:http://www.cocoachina.com/ios/20170612/19485.html

WebView

  • 创建与用法
- (void)loadRequest:(NSURLRequest *)request;
- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
  • UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;
  • UIWebView OC调用JS
  1. stringByEvaluatingJavaScriptFromString:
self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
  • 该方法不能判断调用了一个js方法之后,是否发生了错误。当错误发生时,返回值为nil,而当调用一个方法本身没有返回值时,返回值也为nil,所以无法判断是否调用成功了。
  • 返回值类型为nullable NSString *,就意味着当调用的js方法有返回值时,都以字符串返回,不够灵活。当返回值是一个js的Array时,还需要解析字符串,比较麻烦。
  1. JavaScriptCore(iOS 7.0 +)
  • 其实WebKit都有一个内嵌的js环境,一般我们在页面加载完成之后,获取js上下文,然后通过JSContext的evaluateScript:方法来获取返回值。因为该方法得到的是一个JSValue对象,所以支持JavaScript的Array、Number、String、对象等数据类型
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    //更新标题,这是上面的讲过的方法
    //self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
 
    //获取该UIWebView的javascript上下文
    JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
 
    //这也是一种获取标题的方法。
    JSValue *value = [self.jsContext evaluateScript:@"document.title"];
    //更新标题
    self.navigationItem.title = value.toString;
}
  • 可以通过@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);,设置该block来获取异常
//在调用前,设置异常回调
[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *exception){
        NSLog(@"%@", exception);
}];
//执行方法
JSValue *value = [self.jsContext evaluateScript:@"document.titlexxxx"];
  • UIWebView JS调用OC
  1. Custom URL Scheme(拦截URL)
  • 比如darkangel://。方法是在html或者js中,点击某个按钮触发事件时,跳转到自定义URL Scheme构成的链接,而Objective-C中捕获该链接,从中解析必要的参数,实现JS到OC的一次交互
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    //标准的URL包含scheme、host、port、path、query、fragment等
    NSURL *URL = request.URL;    
    if ([URL.scheme isEqualToString:@"darkangel"]) {
        if ([URL.host isEqualToString:@"smsLogin"]) {
            NSLog(@"短信验证码登录,参数为 %@", URL.query);
            return NO;
        }
    }
    return YES;
}
  • 优点:泛用性强,可以配合h5实现页面动态化
  • 缺点:无法直接获取本次交互的返回值,比较适合单向传参,且不关心回调的情景
  1. JavaScriptCore(iOS 7.0 +)
function share(title, imgUrl, link) {
     //这里需要OC实现
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    //将js的function映射到OC的方法
    [self convertJSFunctionsToOCMethods];
}
 
- (void)convertJSFunctionsToOCMethods
{
    //获取该UIWebview的javascript上下文
    //self持有jsContext
    //@property (nonatomic, strong) JSContext *jsContext;
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
 
    //js调用oc
    //其中share就是js的方法名称,赋给是一个block 里面是oc代码
    //此方法最终将打印出所有接收到的参数,js参数是不固定的
    self.jsContext[@"share"] = ^() {
        NSArray *args = [JSContext currentArguments];//获取到share里的所有参数
        //args中的元素是JSValue,需要转成OC的对象
        NSMutableArray *messages = [NSMutableArray array];
        for (JSValue *obj in args) {
            [messages addObject:[obj toObject]];
        }
        NSLog(@"点击分享js传回的参数:\n%@", messages);
    };
}
  • 上面的代码实现了OC方法替换JS实现。它十分灵活,主要依赖这些Api
@interface JSContext (SubscriptSupport)
/*!
@method
@abstract Get a particular property on the global object.
@result The JSValue for the global object's property.
*/
- (JSValue *)objectForKeyedSubscript:(id)key;
/*!
@method
@abstract Set a particular property on the global object.
*/
- (void)setObject:(id)object forKeyedSubscript:(NSObject*)key;

UIWebView的Cookie管理

Cookie在Web利用的最多的地方,是用来记录各种状态。比如你在Safari中打开百度,然后登陆自己的账号,之后打开所有百度相关的页面,都会是登陆状态,而且当你关了电脑,下次开机再次打开Safari打开百度,会发现还是登陆状态,其实这个就利用了Cookie。Cookie中记录了你百度账号的一些信息、有效期等,也维持了跨域请求时登录状态的统计性

  • Cookie管理
    UIWebView的Cookie管理很简单,一般不需要我们手动操作Cookie,因为所有Cookie都会被[NSHTTPCookieStorage sharedHTTPCookieStorage]这个单例管理,而且UIWebView会自动同步CookieStorage中的Cookie,所以只要我们在Native端,正常登陆退出,h5在适当时候刷新,就可以正确的维持登录状态,不需要做多余的操作
// 可能有一些情况下,我们需要在访问某个链接时,添加一个固定Cookie用来做区分,那么就可以通过header来实现
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];
[request addValue:@"customCookieName=1314521;" forHTTPHeaderField:@"Set-Cookie"];
[self.webView loadRequest:request];
// 也可以主动操作NSHTTPCookieStorage,添加一个自定义Cookie
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{
    NSHTTPCookieName: @"customCookieName", 
    NSHTTPCookieValue: @"1314521", 
    NSHTTPCookieDomain: @".baidu.com",
    NSHTTPCookiePath: @"/"
}];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];    //Cookie存在则覆盖,不存在添加
// 还有一些常用的方法,如读取所有Cookie
NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies;
// Cookie转换成HTTPHeaderFields,并添加到request的header中
//Cookies数组转换为requestHeaderFields
NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
//设置请求头
request.allHTTPHeaderFields = requestHeaderFields;

WKWebView

WKWebView,是Apple于iOS 8.0推出的WebKit中的核心控件,用来替代UIWebView, WKWebView比UIWebView的优势在于:

  • 更多的支持HTML5的特性
  • 高达60fps的滚动刷新率以及内置手势
  • 与Safari相同的JavaScript引擎
  • 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议官方文档说明
  • 可以获取加载进度:estimatedProgress(UIWebView需要调用私有Api)
  1. 创建
/*-initWithFrame: to initialize an instance with the default configuration. 如果使用initWithFrame方法将使用默认的configuration
The initializer copies the specified configuration, so mutating the configuration after invoking the initializer has no effect on the web view. 我们需要先设置configuration,再调用init,在init之后修改configuration则无效
*/
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
  1. 重要属性
// WKWebView 拥有自己的私有存储,它的一些缓存等数据都存在websiteDataStore中
@property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore API_AVAILABLE(macosx(10.11), ios(9.0));
// js->oc的交互,以及注入js代码都会用到它
@interface WKUserContentController : NSObject <NSCoding>
//读取添加过的脚本
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
//添加脚本
- (void)addUserScript:(WKUserScript *)userScript;
//删除所有添加的脚本
- (void)removeAllUserScripts;
//通过window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 来实现js->oc传递消息,并添加handler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
//删除handler
- (void)removeScriptMessageHandlerForName:(NSString *)name;
@end
  1. 具体创建
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc] init];
configuration.userContentController = controller;
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
self.webView.allowsBackForwardNavigationGestures = YES;    //允许右滑返回上个链接,左滑前进
self.webView.allowsLinkPreview = YES; //允许链接3D Touch
self.webView.customUserAgent = @"WebViewDemo/1.0.0"; //自定义UA,UIWebView就没有此功能,后面会讲到通过其他方式实现
self.webView.UIDelegate = self;
self.webView.navigationDelegate = self;
[self.view addSubview:self.webView];
  1. 动态注入js
WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:@"document.cookie = 'DarkAngelCookie=DarkAngel;'" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[controller addUserScript:newCookieScript];
//创建脚本
WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:@"alert(document.cookie);" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
//添加脚本
[controller addUserScript:script];
  1. 加载
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));
  1. 代理
@protocol WKNavigationDelegate;    //类似于UIWebView的加载成功、失败、是否允许跳转等
@protocol WKUIDelegate;    //主要是一些alert、打开新窗口之类的
//下面这2个方法共同对应了UIWebView的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//先:针对一次action来决定是否允许跳转,action中可以获取request,允许与否都需要调用decisionHandler,比如decisionHandler(WKNavigationActionPolicyCancel);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
//后:根据response来决定,是否允许跳转,允许与否都需要调用decisionHandler,如decisionHandler(WKNavigationResponsePolicyAllow);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

//开始加载,对应UIWebView的- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

//加载成功,对应UIWebView的- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;

//加载失败,对应UIWebView的- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
  1. 新属性
@property (nullable, nonatomic, readonly, copy) NSString *title;    //页面的title,终于可以直接获取了
@property (nullable, nonatomic, readonly, copy) NSURL *URL;        //当前webView的URL
@property (nonatomic, readonly, getter=isLoading) BOOL loading;    //是否正在加载
@property (nonatomic, readonly) double estimatedProgress;    //加载的进度
@property (nonatomic, readonly) BOOL canGoBack;    //是否可以后退,跟UIWebView相同
@property (nonatomic, readonly) BOOL canGoForward;    //是否可以前进,跟UIWebView相同
  • OC -> JS
//执行一段js,并将结果返回,如果出错,error则不为空
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id result, NSError * _Nullable error))completionHandler;
  • JS -> OC
<a href="darkangel://smsLogin?username=12323123&code=892845">短信验证登录</a>
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    //可以通过navigationAction.navigationType获取跳转类型,如新链接、后退等
    NSURL *URL = navigationAction.request.URL;
    //判断URL是否符合自定义的URL Scheme
    if ([URL.scheme isEqualToString:@"darkangel"]) {
        //根据不同的业务,来执行对应的操作,且获取参数
        if ([URL.host isEqualToString:@"smsLogin"]) {
            NSString *param = URL.query;
            NSLog(@"短信验证码登录, 参数为%@", param);
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }
    decisionHandler(WKNavigationActionPolicyAllow);
    NSLog(@"%@", NSStringFromSelector(_cmd));
}
  • scriptMessageHandler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
- (void)removeScriptMessageHandlerForName:(NSString *)name;
// 使用
[controller addScriptMessageHandler:self name:@"currentCookies"]; //这里self要遵循协 WKScriptMessageHandler
window.webkit.messageHandlers.currentCookies.postMessage(document.cookie);
// 我在OC中将会收到WKScriptMessageHandler的回调
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"currentCookies"]) {
        NSString *cookiesStr = message.body;    //message.body返回的是一个id类型的对象,所以可以支持很多种js的参数类型(js的function除外)
        NSLog(@"当前的cookie为: %@", cookiesStr);
    }
}
- (void)dealloc {
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"currentCookies"];
}
    原文作者:天马行猿
    原文地址: https://www.jianshu.com/p/cbcc755336aa
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞