iOS WKWebViewは実戦編に似合います。
一、Cookie適合
1.現状
WKWebView適合の中で一番厄介なのはクッキーの同期問題です。
WKWebViewは独立したメモリコントロールを採用しており、従来のUICWebViewとは通信していない。
iOS 11以降は、WKHTTPCookie Storeを開放して開発者を同期させるが、低バージョンの同期問題を考慮し、各角度からcookie同期問題を考慮する必要がある。
2.同期クッキー(NSHTTPCookie Storrage->WKHT TPCookie Store)
iOS 11+
WKHTTPCookie Storeを直接使用して値を設定できます。wkwebviewを作成する時に同期してもいいです。要求時にもいいです。
例えば、オンラインページを要求するときは、クッキーでアイデンティティを認証します。初期化時に同期しないと、ページを要求することができる場合は401です。
iOS 11-
先端でjs注入クッキーを実行し、要求時に実行します。
したがって、要求時にrequestを作成するにはcookieを設定し、かつloadRequestを設定する必要があります。
wkwebviewによって生成されるクッキーは、NSHT TPCookie Storrageに同期する必要がある場合もあります。
iOS 11+はWKHTTPCookie Storeで直接同期できます。
iOS 11-Js端子で取得でき、NSHT TPCookie Storrageに同期するようにトリガします。
しかし、js同期方式はhttpOnlyと同期できないので、本当に出会いました。サーバーなどと一緒にこの同期をします。
二、JSとNative通信
1.Native呼び出しJS
コードの準備が完了したらAPIを起動すればいいです。コールバック関数はjs実行結果またはエラー情報を受信できます。So Easy。
実は先にJS方法を注入して、JS端末に呼び出しを提供することができます。
例えば、bridgeをそのままWKの実行環境に注入するフレームがあります。先端からJSを導入するのではなく、先端のJSがオンラインローディングであると仮定して、JSサーバーがハングアップされたり、ネットワーク上の問題が発生すると、先端ページはNaitveのBridge通信能力を失ってしまいます。
3-1.プロキシ類を準備する
エージェントはWKScript Message Handlerを実現します。
適切なタイミング(一般初期化)でプロキシクラスを設定し、nameを指定します。
上記の文を実行すると、JS側にオブジェクト「window.webkit.message Handlers.bridge」を注入します。
そして、native端はWKScript Messageのbody属性によって着信値を得ることができる。
ここではなぜWeakScript Message Delegateを使って、delegateをセットしてselfを指していますか?
ヒント:NSTimerの循環参照問題を参照することができます。
3-6.完全な例
iOS 8 beta 5の前に、JSとNativeのような通信設定はできませんので、ライフサイクルの中でURLのブロックを使ってデータを解析して効果を達成することができます。ここでは詳細な説明はしません。インターネット上のUICbviewのようなブリッジの原理を自分で参照してください。
三、実戦技術
1.UserAgentの設定
UAを追加
実際の過程では元のUAだけで追加操作をしたほうがいいです。全部交換するとサーバーが拒否する可能性があります。(セキュリティポリシー)
ログの赤い線の部分はシミュレータ全体のUAで、緑の部門はUAの中のApplication Nameの部分です。
iOS 9では、WKWebviewはAPIを提供しています。upの中のApple Nameを設定することができます。
iOS 9以上で直接wkwebviewのcustomUserAgentを指定できます。iOS 9以下であれば、NSUserDefaultsを設定します。
wkwebviewはページのロードの進捗を監視できます。ブラウザでページの進捗バーの表示を開くようです。
ページ切り替え時にも自動的にページに設定されているtitleを更新します。実際のプロジェクトで容器のtitleを動的に切り替えることができます。例えば、切り替えのtitleによってnavigationItem.titleを設定します。
原理は直接KVO方式によって値の変化を監督し、その後、フィードバックにおいて関連ロジックを処理する。
ここでは自分が実現したbridge通信フレームを紹介します。先端は容器に関心がなく、フレーム層にフィットします。
呼び出したNativeコードは以下の通りです。
フレームの原理ビデオを取得するにはクリックしてください。
ここでiOS WKWebViewの実戦編にふさわしい文章を紹介します。iOS WKWebViewの適した内容については、以前の文章を検索してください。または下記の関連記事を引き続きご覧ください。これからもよろしくお願いします。
1.現状
WKWebView適合の中で一番厄介なのはクッキーの同期問題です。
WKWebViewは独立したメモリコントロールを採用しており、従来のUICWebViewとは通信していない。
iOS 11以降は、WKHTTPCookie Storeを開放して開発者を同期させるが、低バージョンの同期問題を考慮し、各角度からcookie同期問題を考慮する必要がある。
2.同期クッキー(NSHTTPCookie Storrage->WKHT TPCookie Store)
iOS 11+
WKHTTPCookie Storeを直接使用して値を設定できます。wkwebviewを作成する時に同期してもいいです。要求時にもいいです。
// iOS11 HTTPCookieStorag WKHTTPCookieStore
WKHTTPCookieStore *cookieStore = self.wkWebView.configuration.websiteDataStore.httpCookieStore;
- (void)syncCookiesToWKCookieStore:(WKHTTPCookieStore *)cookieStore API_AVAILABLE(ios(11.0)){
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
if (cookies.count == 0) return;
for (NSHTTPCookie *cookie in cookies) {
[cookieStore setCookie:cookie completionHandler:^{
if ([cookies.lastObject isEqual:cookie]) {
[self wkwebviewSetCookieSuccess];
}
}];
}
}
同期cookieは、wkwebviewを初期化する際にも、要求時にも可能である。初期化時に同期させることで、起起起)ページ要求時にクッキーを持参することができます。例えば、オンラインページを要求するときは、クッキーでアイデンティティを認証します。初期化時に同期しないと、ページを要求することができる場合は401です。
iOS 11-
先端でjs注入クッキーを実行し、要求時に実行します。
//wkwebview JS
- (void)injectCookiesLT11 {
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:[self cookieString] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[self.wkWebView.configuration.userContentController addUserScript:cookieScript];
}
// NSHTTPCookieStorage, JS
- (NSString *)cookieString {
NSMutableString *script = [NSMutableString string];
[script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );
"];
for (NSHTTPCookie *cookie in NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies) {
// Skip cookies that will break our script
if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
continue;
}
[script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };
", cookie.name, [self formatCookie:cookie]];
}
return script;
}
//Format cookie js
- (NSString *)formatCookie:(NSHTTPCookie *)cookie {
NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
cookie.name,
cookie.value,
cookie.domain,
cookie.path ?: @"/"];
if (cookie.secure) {
string = [string stringByAppendingString:@";secure=true"];
}
return string;
}
しかし、上記の方法は、jsを実行しても、最初のページ要求にクッキーが付いているとは保証できません。したがって、要求時にrequestを作成するにはcookieを設定し、かつloadRequestを設定する必要があります。
-(void)injectRequestCookieLT11:(NSMutableURLRequest*)mutableRequest {
// iOS11 , cookie
NSArray *cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies;
NSMutableArray *mutableCookies = @[].mutableCopy;
for (NSHTTPCookie *cookie in cookies) {
[mutableCookies addObject:cookie];
}
// Cookies requestHeaderFields
NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:(NSArray *)mutableCookies];
//
mutableRequest.allHTTPHeaderFields = requestHeaderFields;
}
3.逆同期クッキー(WKHTTPCookie Store-)NSHT TPCookie Storrage)wkwebviewによって生成されるクッキーは、NSHT TPCookie Storrageに同期する必要がある場合もあります。
iOS 11+はWKHTTPCookie Storeで直接同期できます。
iOS 11-Js端子で取得でき、NSHT TPCookie Storrageに同期するようにトリガします。
しかし、js同期方式はhttpOnlyと同期できないので、本当に出会いました。サーバーなどと一緒にこの同期をします。
二、JSとNative通信
1.Native呼び出しJS
コードの準備が完了したらAPIを起動すればいいです。コールバック関数はjs実行結果またはエラー情報を受信できます。So Easy。
[self.wkWebView evaluateJavaScript:jsCode completionHandler:^(id object, NSError *error){}];
2.JS注入実は先にJS方法を注入して、JS端末に呼び出しを提供することができます。
例えば、bridgeをそのままWKの実行環境に注入するフレームがあります。先端からJSを導入するのではなく、先端のJSがオンラインローディングであると仮定して、JSサーバーがハングアップされたり、ネットワーク上の問題が発生すると、先端ページはNaitveのBridge通信能力を失ってしまいます。
-(instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
//WKUserScriptInjectionTime
typedef NS_ENUM(NSInteger, WKUserScriptInjectionTime) {
WKUserScriptInjectionTimeAtDocumentStart, /** **/
WKUserScriptInjectionTimeAtDocumentEnd /** **/
} API_AVAILABLE(macos(10.10), ios(8.0));
3.JS呼び出しNative3-1.プロキシ類を準備する
エージェントはWKScript Message Handlerを実現します。
@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>
@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
@end
WKScript Message Handlerは一つの方法です。
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
self = [super init];
if (self) {
_scriptDelegate = scriptDelegate;
}
return self;
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
[self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}
3-2.プロキシ類の設定適切なタイミング(一般初期化)でプロキシクラスを設定し、nameを指定します。
NSString* MessageHandlerName = @"bridge";
[config.userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:MessageHandlerName];
3-3.bridgeの使用(JS端)上記の文を実行すると、JS側にオブジェクト「window.webkit.message Handlers.bridge」を注入します。
//JS , String,
window.webkit.messageHandlers.bridge.postMessage("type");
3-4.Native端末メッセージの受信そして、native端はWKScript Messageのbody属性によって着信値を得ることができる。
- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:HistoryBridageName]) {
} else if ([message.name isEqualToString:MessageHandlerName]) {
[self jsToNativeImpl:message.body];
}
}
3-5.思考問題ここではなぜWeakScript Message Delegateを使って、delegateをセットしてselfを指していますか?
ヒント:NSTimerの循環参照問題を参照することができます。
3-6.完全な例
-(void)_defaultConfig{
WKWebViewConfiguration* config = [WKWebViewConfiguration new];
…… ……
…… ……
WKUserContentController* userController = [[WKUserContentController alloc] init];
config.userContentController = userController;
[self injectHistoryBridge:config];
…… ……
…… ……
}
-(void)injectHistoryBridge:(WKWebViewConfiguration*)config{
[config.userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:HistoryBridageName];
NSString *_jsSource = [NSString stringWithFormat:
@"(function(history) {
"
" function notify(type) {
"
" setTimeout(function() {
"
" window.webkit.messageHandlers.%@.postMessage(type)
"
" }, 0)
"
" }
"
" function shim(f) {
"
" return function pushState() {
"
" notify('other')
"
" return f.apply(history, arguments)
"
" }
"
" }
"
" history.pushState = shim(history.pushState)
"
" history.replaceState = shim(history.replaceState)
"
" window.addEventListener('popstate', function() {
"
" notify('backforward')
"
" })
"
"})(window.history)
", HistoryBridageName
];
WKUserScript *script = [[WKUserScript alloc] initWithSource:_jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
[config.userContentController addUserScript:script];
}
3-7.その他の問題iOS 8 beta 5の前に、JSとNativeのような通信設定はできませんので、ライフサイクルの中でURLのブロックを使ってデータを解析して効果を達成することができます。ここでは詳細な説明はしません。インターネット上のUICbviewのようなブリッジの原理を自分で参照してください。
三、実戦技術
1.UserAgentの設定
UAを追加
実際の過程では元のUAだけで追加操作をしたほうがいいです。全部交換するとサーバーが拒否する可能性があります。(セキュリティポリシー)
ログの赤い線の部分はシミュレータ全体のUAで、緑の部門はUAの中のApplication Nameの部分です。
iOS 9では、WKWebviewはAPIを提供しています。upの中のApple Nameを設定することができます。
config.applicationNameForUserAgent = [NSString stringWithFormat:@"%@ %@", config.applicationNameForUserAgent, @"arleneConfig"];
全部置換UAiOS 9以上で直接wkwebviewのcustomUserAgentを指定できます。iOS 9以下であれば、NSUserDefaultsを設定します。
if (@available(iOS 9.0, *)) {
self.wkWebView.customUserAgent = @"Hello My UserAgent";
}else{
[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":@"Hello My UserAgent"}];
[[NSUserDefaults standardUserDefaults] synchronize];
}
2.進捗状況とページのtitle変化を監督するwkwebviewはページのロードの進捗を監視できます。ブラウザでページの進捗バーの表示を開くようです。
ページ切り替え時にも自動的にページに設定されているtitleを更新します。実際のプロジェクトで容器のtitleを動的に切り替えることができます。例えば、切り替えのtitleによってnavigationItem.titleを設定します。
原理は直接KVO方式によって値の変化を監督し、その後、フィードバックにおいて関連ロジックを処理する。
//kvo
[self.webView addObserver:self
forKeyPath:@"estimatedProgress"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:nil];
//kvo title
[self.webView addObserver:self
forKeyPath:@"title"
options:NSKeyValueObservingOptionNew
context:nil];
/** KVO **/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
ALLOGF(@"Progress--->%@",[NSNumber numberWithDouble:self.webView.estimatedProgress]);
}else if([keyPath isEqualToString:@"title"]
&& object == self.webview){
self.navigationItem.title = self.webView.title;
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
/** **/
[self.webView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
[self.webView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(title))];
3.Bridge通信の実戦ここでは自分が実現したbridge通信フレームを紹介します。先端は容器に関心がなく、フレーム層にフィットします。
import {WebBridge} from 'XXX'
/**
* : WebBridge.call(taskName,options,callback)
* :
* taskName String task , Native
* options Object
* callback function
*.
* json object native
**/
WebBridge.call("Alert",{"content":" ","btn":"btn "},function(json){
console.log("call back is here",JSON.stringify(json));
});
NativeのAlertコントロールを上から呼び出し、呼び出し結果を返します。呼び出したNativeコードは以下の通りです。
//AlertTask.m
#import "AlertTask.h"
#import <lib-base/ALBaseConstants.h>
@interface AlertTask (){}
@property (nonatomic,weak) ArleneWebViewController* mCtrl;
@end
@implementation AlertTask
-(instancetype)initWithContext:(ArleneWebViewController*)controller{
self = [super init];
self.mCtrl = controller;
return self;
}
-(NSString*)taskName{
return @"Alert";
}
-(void)doTask:(NSDictionary*)params{
ALShowAlert(@"Title",@"message");// Alert
NSMutableDictionary* callback = [ArleneTaskUtils basicCallback:params];// callback
[callback addEntriesFromDictionary:params];
[self.mCtrl callJS:callback];//
}
@end
具体的な実現原理は下のビデオリンクをクリックすることができます。フレームの原理ビデオを取得するにはクリックしてください。
ここでiOS WKWebViewの実戦編にふさわしい文章を紹介します。iOS WKWebViewの適した内容については、以前の文章を検索してください。または下記の関連記事を引き続きご覧ください。これからもよろしくお願いします。