注,本文轉(zhuǎn)載自:http://www.cocoachina.com/ios/20170612/19485.html
WebView
- 創(chuàng)建與用法
- (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調(diào)用JS
- stringByEvaluatingJavaScriptFromString:
self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
- 該方法不能判斷調(diào)用了一個(gè)js方法之后镀梭,是否發(fā)生了錯(cuò)誤刀森。當(dāng)錯(cuò)誤發(fā)生時(shí),返回值為nil报账,而當(dāng)調(diào)用一個(gè)方法本身沒有返回值時(shí)研底,返回值也為nil,所以無(wú)法判斷是否調(diào)用成功了透罢。
- 返回值類型為nullable NSString *榜晦,就意味著當(dāng)調(diào)用的js方法有返回值時(shí),都以字符串返回羽圃,不夠靈活乾胶。當(dāng)返回值是一個(gè)js的Array時(shí),還需要解析字符串朽寞,比較麻煩识窿。
- JavaScriptCore(iOS 7.0 +)
- 其實(shí)WebKit都有一個(gè)內(nèi)嵌的js環(huán)境,一般我們?cè)陧?yè)面加載完成之后脑融,獲取js上下文喻频,然后通過(guò)JSContext的evaluateScript:方法來(lái)獲取返回值。因?yàn)樵摲椒ǖ玫降氖且粋€(gè)JSValue對(duì)象肘迎,所以支持JavaScript的Array甥温、Number、String妓布、對(duì)象等數(shù)據(jù)類型
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//更新標(biāo)題窿侈,這是上面的講過(guò)的方法
//self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
//獲取該UIWebView的javascript上下文
JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//這也是一種獲取標(biāo)題的方法。
JSValue *value = [self.jsContext evaluateScript:@"document.title"];
//更新標(biāo)題
self.navigationItem.title = value.toString;
}
- 可以通過(guò)@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);秋茫,設(shè)置該block來(lái)獲取異常
//在調(diào)用前史简,設(shè)置異常回調(diào)
[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *exception){
NSLog(@"%@", exception);
}];
//執(zhí)行方法
JSValue *value = [self.jsContext evaluateScript:@"document.titlexxxx"];
- UIWebView JS調(diào)用OC
- Custom URL Scheme(攔截URL)
- 比如darkangel://。方法是在html或者js中圆兵,點(diǎn)擊某個(gè)按鈕觸發(fā)事件時(shí)跺讯,跳轉(zhuǎn)到自定義URL Scheme構(gòu)成的鏈接,而Objective-C中捕獲該鏈接殉农,從中解析必要的參數(shù)刀脏,實(shí)現(xiàn)JS到OC的一次交互
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
//標(biāo)準(zhǔn)的URL包含scheme、host超凳、port愈污、path、query轮傍、fragment等
NSURL *URL = request.URL;
if ([URL.scheme isEqualToString:@"darkangel"]) {
if ([URL.host isEqualToString:@"smsLogin"]) {
NSLog(@"短信驗(yàn)證碼登錄暂雹,參數(shù)為 %@", URL.query);
return NO;
}
}
return YES;
}
- 優(yōu)點(diǎn):泛用性強(qiáng),可以配合h5實(shí)現(xiàn)頁(yè)面動(dòng)態(tài)化
- 缺點(diǎn):無(wú)法直接獲取本次交互的返回值创夜,比較適合單向傳參杭跪,且不關(guān)心回調(diào)的情景
- JavaScriptCore(iOS 7.0 +)
function share(title, imgUrl, link) {
//這里需要OC實(shí)現(xiàn)
}
- (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調(diào)用oc
//其中share就是js的方法名稱,賦給是一個(gè)block 里面是oc代碼
//此方法最終將打印出所有接收到的參數(shù)驰吓,js參數(shù)是不固定的
self.jsContext[@"share"] = ^() {
NSArray *args = [JSContext currentArguments];//獲取到share里的所有參數(shù)
//args中的元素是JSValue涧尿,需要轉(zhuǎn)成OC的對(duì)象
NSMutableArray *messages = [NSMutableArray array];
for (JSValue *obj in args) {
[messages addObject:[obj toObject]];
}
NSLog(@"點(diǎn)擊分享js傳回的參數(shù):\n%@", messages);
};
}
- 上面的代碼實(shí)現(xiàn)了OC方法替換JS實(shí)現(xiàn)。它十分靈活檬贰,主要依賴這些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利用的最多的地方姑廉,是用來(lái)記錄各種狀態(tài)。比如你在Safari中打開百度翁涤,然后登陸自己的賬號(hào)桥言,之后打開所有百度相關(guān)的頁(yè)面,都會(huì)是登陸狀態(tài)迷雪,而且當(dāng)你關(guān)了電腦,下次開機(jī)再次打開Safari打開百度虫蝶,會(huì)發(fā)現(xiàn)還是登陸狀態(tài)章咧,其實(shí)這個(gè)就利用了Cookie。Cookie中記錄了你百度賬號(hào)的一些信息能真、有效期等赁严,也維持了跨域請(qǐng)求時(shí)登錄狀態(tài)的統(tǒng)計(jì)性
- Cookie管理
UIWebView的Cookie管理很簡(jiǎn)單,一般不需要我們手動(dòng)操作Cookie粉铐,因?yàn)樗蠧ookie都會(huì)被[NSHTTPCookieStorage sharedHTTPCookieStorage]這個(gè)單例管理疼约,而且UIWebView會(huì)自動(dòng)同步CookieStorage中的Cookie,所以只要我們?cè)贜ative端蝙泼,正常登陸退出程剥,h5在適當(dāng)時(shí)候刷新,就可以正確的維持登錄狀態(tài)汤踏,不需要做多余的操作
// 可能有一些情況下织鲸,我們需要在訪問(wèn)某個(gè)鏈接時(shí)舔腾,添加一個(gè)固定Cookie用來(lái)做區(qū)分,那么就可以通過(guò)header來(lái)實(shí)現(xiàn)
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];
[request addValue:@"customCookieName=1314521;" forHTTPHeaderField:@"Set-Cookie"];
[self.webView loadRequest:request];
// 也可以主動(dòng)操作NSHTTPCookieStorage搂擦,添加一個(gè)自定義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轉(zhuǎn)換成HTTPHeaderFields瀑踢,并添加到request的header中
//Cookies數(shù)組轉(zhuǎn)換為requestHeaderFields
NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
//設(shè)置請(qǐng)求頭
request.allHTTPHeaderFields = requestHeaderFields;
WKWebView
WKWebView,是Apple于iOS 8.0推出的WebKit中的核心控件扳还,用來(lái)替代UIWebView, WKWebView比UIWebView的優(yōu)勢(shì)在于:
- 更多的支持HTML5的特性
- 高達(dá)60fps的滾動(dòng)刷新率以及內(nèi)置手勢(shì)
- 與Safari相同的JavaScript引擎
- 將UIWebViewDelegate與UIWebView拆分成了14類與3個(gè)協(xié)議官方文檔說(shuō)明
- 可以獲取加載進(jìn)度:estimatedProgress(UIWebView需要調(diào)用私有Api)
- 創(chuàng)建
/*-initWithFrame: to initialize an instance with the default configuration. 如果使用initWithFrame方法將使用默認(rèn)的configuration
The initializer copies the specified configuration, so mutating the configuration after invoking the initializer has no effect on the web view. 我們需要先設(shè)置configuration,再調(diào)用init橱夭,在init之后修改configuration則無(wú)效
*/
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
- 重要屬性
// WKWebView 擁有自己的私有存儲(chǔ)氨距,它的一些緩存等數(shù)據(jù)都存在websiteDataStore中
@property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore API_AVAILABLE(macosx(10.11), ios(9.0));
// js->oc的交互,以及注入js代碼都會(huì)用到它
@interface WKUserContentController : NSObject <NSCoding>
//讀取添加過(guò)的腳本
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
//添加腳本
- (void)addUserScript:(WKUserScript *)userScript;
//刪除所有添加的腳本
- (void)removeAllUserScripts;
//通過(guò)window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 來(lái)實(shí)現(xiàn)js->oc傳遞消息徘钥,并添加handler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
//刪除handler
- (void)removeScriptMessageHandlerForName:(NSString *)name;
@end
- 具體創(chuàng)建
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; //允許右滑返回上個(gè)鏈接衔蹲,左滑前進(jìn)
self.webView.allowsLinkPreview = YES; //允許鏈接3D Touch
self.webView.customUserAgent = @"WebViewDemo/1.0.0"; //自定義UA,UIWebView就沒有此功能呈础,后面會(huì)講到通過(guò)其他方式實(shí)現(xiàn)
self.webView.UIDelegate = self;
self.webView.navigationDelegate = self;
[self.view addSubview:self.webView];
- 動(dòng)態(tài)注入js
WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:@"document.cookie = 'DarkAngelCookie=DarkAngel;'" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[controller addUserScript:newCookieScript];
//創(chuàng)建腳本
WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:@"alert(document.cookie);" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
//添加腳本
[controller addUserScript:script];
- 加載
- (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));
- 代理
@protocol WKNavigationDelegate; //類似于UIWebView的加載成功舆驶、失敗、是否允許跳轉(zhuǎn)等
@protocol WKUIDelegate; //主要是一些alert而钞、打開新窗口之類的
//下面這2個(gè)方法共同對(duì)應(yīng)了UIWebView的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//先:針對(duì)一次action來(lái)決定是否允許跳轉(zhuǎn)沙廉,action中可以獲取request,允許與否都需要調(diào)用decisionHandler臼节,比如decisionHandler(WKNavigationActionPolicyCancel);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler撬陵;
//后:根據(jù)response來(lái)決定,是否允許跳轉(zhuǎn)网缝,允許與否都需要調(diào)用decisionHandler巨税,如decisionHandler(WKNavigationResponsePolicyAllow);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
//開始加載,對(duì)應(yīng)UIWebView的- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
//加載成功粉臊,對(duì)應(yīng)UIWebView的- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
//加載失敗草添,對(duì)應(yīng)UIWebView的- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
- 新屬性
@property (nullable, nonatomic, readonly, copy) NSString *title; //頁(yè)面的title,終于可以直接獲取了
@property (nullable, nonatomic, readonly, copy) NSURL *URL; //當(dāng)前webView的URL
@property (nonatomic, readonly, getter=isLoading) BOOL loading; //是否正在加載
@property (nonatomic, readonly) double estimatedProgress; //加載的進(jìn)度
@property (nonatomic, readonly) BOOL canGoBack; //是否可以后退扼仲,跟UIWebView相同
@property (nonatomic, readonly) BOOL canGoForward; //是否可以前進(jìn)远寸,跟UIWebView相同
- OC -> JS
//執(zhí)行一段js,并將結(jié)果返回屠凶,如果出錯(cuò)驰后,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">短信驗(yàn)證登錄</a>
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
//可以通過(guò)navigationAction.navigationType獲取跳轉(zhuǎn)類型,如新鏈接矗愧、后退等
NSURL *URL = navigationAction.request.URL;
//判斷URL是否符合自定義的URL Scheme
if ([URL.scheme isEqualToString:@"darkangel"]) {
//根據(jù)不同的業(yè)務(wù)灶芝,來(lái)執(zhí)行對(duì)應(yīng)的操作,且獲取參數(shù)
if ([URL.host isEqualToString:@"smsLogin"]) {
NSString *param = URL.query;
NSLog(@"短信驗(yàn)證碼登錄, 參數(shù)為%@", 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要遵循協(xié) WKScriptMessageHandler
window.webkit.messageHandlers.currentCookies.postMessage(document.cookie);
// 我在OC中將會(huì)收到WKScriptMessageHandler的回調(diào)
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"currentCookies"]) {
NSString *cookiesStr = message.body; //message.body返回的是一個(gè)id類型的對(duì)象,所以可以支持很多種js的參數(shù)類型(js的function除外)
NSLog(@"當(dāng)前的cookie為: %@", cookiesStr);
}
}
- (void)dealloc {
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"currentCookies"];
}