let uiwebview as wkwebview
TRANSCRIPT
from: WWDC2014 Introducing the Modern WebKit API
• iOS 2.0 からお馴染み • JavaScript が Safari より遅い
from: WWDC2014 Introducing the Modern WebKit API
• iOS 2.0 からお馴染み • JavaScript が Safari より遅い
• iOS 8.0 から導入 • Safari と同じ JavaScript エンジン! • ページ遷移ジェスチャーにも対応 • その他も何かと高性能で良い
UIWebView (親: UIView) WKWebView (親: UIView)var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var title: String? { get } var URL: NSURL? { get } var estimatedProgress: Double { get }
func loadRequest(request: NSURLRequest) func reload() func stopLoading() func goBack() func goForward()
func loadRequest(request: NSURLRequest) -> WKNavigation?
func reload() -> WKNavigation? func stopLoading() func goBack() -> WKNavigation? func goForward() -> WKNavigation?
func stringByEvaluatingJavaScriptFromString (script: String) -> String?
func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject!, NSError!) -> Void)?)
weak var delegate: UIWebViewDelegate? weak var navigationDelegate: WKNavigationDelegate?
weak var UIDelegate: WKUIDelegate?
インターフェースは似ているが互換性はない
UIWebView (親: UIView) WKWebView (親: UIView)var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var title: String? { get } var URL: NSURL? { get } var estimatedProgress: Double { get }
func loadRequest(request: NSURLRequest) func reload() func stopLoading() func goBack() func goForward()
func loadRequest(request: NSURLRequest) -> WKNavigation?
func reload() -> WKNavigation? func stopLoading() func goBack() -> WKNavigation? func goForward() -> WKNavigation?
func stringByEvaluatingJavaScriptFromString (script: String) -> String?
func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject!, NSError!) -> Void)?)
weak var delegate: UIWebViewDelegate? weak var navigationDelegate: WKNavigationDelegate?
weak var UIDelegate: WKUIDelegate?
インターフェースは似ているが互換性はない
WKWebView にしかないプロパティ
UIWebView (親: UIView) WKWebView (親: UIView)var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var title: String? { get } var URL: NSURL? { get } var estimatedProgress: Double { get }
func loadRequest(request: NSURLRequest) func reload() func stopLoading() func goBack() func goForward()
func loadRequest(request: NSURLRequest) -> WKNavigation?
func reload() -> WKNavigation? func stopLoading() func goBack() -> WKNavigation? func goForward() -> WKNavigation?
func stringByEvaluatingJavaScriptFromString (script: String) -> String?
func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject!, NSError!) -> Void)?)
weak var delegate: UIWebViewDelegate? weak var navigationDelegate: WKNavigationDelegate?
weak var UIDelegate: WKUIDelegate?
インターフェースは似ているが互換性はない
load系 は WKNavigation? 型の戻り値がある
UIWebView (親: UIView) WKWebView (親: UIView)var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var title: String? { get } var URL: NSURL? { get } var estimatedProgress: Double { get }
func loadRequest(request: NSURLRequest) func reload() func stopLoading() func goBack() func goForward()
func loadRequest(request: NSURLRequest) -> WKNavigation?
func reload() -> WKNavigation? func stopLoading() func goBack() -> WKNavigation? func goForward() -> WKNavigation?
func stringByEvaluatingJavaScriptFromString (script: String) -> String?
func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject!, NSError!) -> Void)?)
weak var delegate: UIWebViewDelegate? weak var navigationDelegate: WKNavigationDelegate?
weak var UIDelegate: WKUIDelegate?
インターフェースは似ているが互換性はない
JavaScript はコールバック形式
UIWebView (親: UIView) WKWebView (親: UIView)var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var title: String? { get } var URL: NSURL? { get } var estimatedProgress: Double { get }
func loadRequest(request: NSURLRequest) func reload() func stopLoading() func goBack() func goForward()
func loadRequest(request: NSURLRequest) -> WKNavigation?
func reload() -> WKNavigation? func stopLoading() func goBack() -> WKNavigation? func goForward() -> WKNavigation?
func stringByEvaluatingJavaScriptFromString (script: String) -> String?
func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject!, NSError!) -> Void)?)
weak var delegate: UIWebViewDelegate? weak var navigationDelegate: WKNavigationDelegate?
weak var UIDelegate: WKUIDelegate?
インターフェースは似ているが互換性はない
delegateが2種類ある(IFもだいぶ違う)
class MyViewController: UIViewController { var wkWebView: WKWebView? var uiWebView: UIWebView? override func viewDidLoad() { super.viewDidLoad() if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: ) self.view.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: ) self.view.addSubview(uiWebView!) } } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let URL = NSURL(string: "http://yahoo.co.jp") let req = NSURLRequest(URL: URL!) if(wkWebView != nil) { wkWebView!.loadRequest(req) } else { uiWebView!.loadRequest(req) } }
ナイーブな下位互換対応をすると…
class MyViewController: UIViewController { var wkWebView: WKWebView? var uiWebView: UIWebView? override func viewDidLoad() { super.viewDidLoad() if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: ) self.view.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: ) self.view.addSubview(uiWebView!) } } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let URL = NSURL(string: "http://yahoo.co.jp") let req = NSURLRequest(URL: URL!) if(wkWebView != nil) { wkWebView!.loadRequest(req) } else { uiWebView!.loadRequest(req) } }
ナイーブな下位互換対応をすると…
UIWebView / WKWebView の両方を変数で宣言
class MyViewController: UIViewController { var wkWebView: WKWebView? var uiWebView: UIWebView? override func viewDidLoad() { super.viewDidLoad() if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: ) self.view.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: ) self.view.addSubview(uiWebView!) } } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let URL = NSURL(string: "http://yahoo.co.jp") let req = NSURLRequest(URL: URL!) if(wkWebView != nil) { wkWebView!.loadRequest(req) } else { uiWebView!.loadRequest(req) } }
ナイーブな下位互換対応をすると…
対応/非対応に応じて一方にインスタンスを入れる
class MyViewController: UIViewController { var wkWebView: WKWebView? var uiWebView: UIWebView? override func viewDidLoad() { super.viewDidLoad() if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: ) self.view.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: ) self.view.addSubview(uiWebView!) } } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let URL = NSURL(string: "http://yahoo.co.jp") let req = NSURLRequest(URL: URL!) if(wkWebView != nil) { wkWebView!.loadRequest(req) } else { uiWebView!.loadRequest(req) } }
ナイーブな下位互換対応をすると…
そして毎回分岐して同じ処理を2度書くことに…
バージョン分岐だらけのコードは危険
• コントローラやモデルの本来の役割が見えにくくなる。
• 古いコードが山積するといずれ誰も手に負えなくなる。
• 下位バージョンのサポートを切るときにも不必要なコー
ドが残りがち。
2015/02 時点でも iOS 8 のシェアは 72%。 100万 ユーザいれば 28万 は iOS 7 以前。 大きなユーザ数を持つサービスには重大な問題。
As measured by the App Store on February 2, 2015.
下位互換対応は「隠蔽」せよ
• コントローラやモデルなどの上位層からはバージョン分岐を意識しなくていいようにする。
• 混み入った分岐はどこかにまとめておいて、いつでも簡単に切り離せるようにしておく。
• 広範囲で使用されるものでも、依存性は最小限に。
2014-01-15 @ potatotips 3Method Swizzling で iOS 7 コードを iOS 6 でも動かす
方針1: ラッパーで包むIF は WKWebView と共通にする
WKWebViewUIWebView
前処理
iOS 8 以上 iOS 7 以下
WKWebViewWrapper : UIView
class WKWebViewWrapper: UIView { // どちらか一方はnil var wkWebView: WKWebView? var uiWebView: UIWebView? override init(frame: CGRect) { super.init(frame: frame) if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: self.bounds) self.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: self.bounds) self.addSubview(uiWebView!) } }
func loadRequest(request: NSURLRequest) -> WKNavigation? { if(wkWebView != nil) { return wkWebView!.loadRequest(request); } else { uiWebView!.loadRequest(request) return nil } }
内部の実装イメージ
class WKWebViewWrapper: UIView { // どちらか一方はnil var wkWebView: WKWebView? var uiWebView: UIWebView? override init(frame: CGRect) { super.init(frame: frame) if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: self.bounds) self.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: self.bounds) self.addSubview(uiWebView!) } }
func loadRequest(request: NSURLRequest) -> WKNavigation? { if(wkWebView != nil) { return wkWebView!.loadRequest(request); } else { uiWebView!.loadRequest(request) return nil } }
内部の実装イメージ
UIWebView / WKWebView の両方を変数で宣言
class WKWebViewWrapper: UIView { // どちらか一方はnil var wkWebView: WKWebView? var uiWebView: UIWebView? override init(frame: CGRect) { super.init(frame: frame) if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: self.bounds) self.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: self.bounds) self.addSubview(uiWebView!) } }
func loadRequest(request: NSURLRequest) -> WKNavigation? { if(wkWebView != nil) { return wkWebView!.loadRequest(request); } else { uiWebView!.loadRequest(request) return nil } }
内部の実装イメージ
対応/非対応に応じて一方にインスタンスを入れる
class WKWebViewWrapper: UIView { // どちらか一方はnil var wkWebView: WKWebView? var uiWebView: UIWebView? override init(frame: CGRect) { super.init(frame: frame) if NSClassFromString("WKWebView") != nil { wkWebView = WKWebView(frame: self.bounds) self.addSubview(wkWebView!) } else { uiWebView = UIWebView(frame: self.bounds) self.addSubview(uiWebView!) } }
func loadRequest(request: NSURLRequest) -> WKNavigation? { if(wkWebView != nil) { return wkWebView!.loadRequest(request); } else { uiWebView!.loadRequest(request) return nil } }
内部の実装イメージ
すべてのメソッドで分岐処理
Why Obj-C ?UIWebView WKWebView
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var title: String? { get } var URL: NSURL? { get } var estimatedProgress: Double { get }
func loadRequest(request: NSURLRequest) func reload() func stopLoading() func goBack() func goForward()
func loadRequest(request: NSURLRequest) -> WKNavigation?
func reload() -> WKNavigation? func stopLoading() func goBack() -> WKNavigation? func goForward() -> WKNavigation?
func stringByEvaluatingJavaScriptFromString (script: String) -> String?
func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject!, NSError!) -> Void)?)
weak var delegate: UIWebViewDelegate? weak var navigationDelegate: WKNavigationDelegate?
weak var UIDelegate: WKUIDelegate?
Why Obj-C ?UIWebView WKWebView
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var canGoBack: Bool { get } var canGoForward: Bool { get } var loading: Bool { get }
var title: String? { get } var URL: NSURL? { get } var estimatedProgress: Double { get }
func loadRequest(request: NSURLRequest) func reload() func stopLoading() func goBack() func goForward()
func loadRequest(request: NSURLRequest) -> WKNavigation?
func reload() -> WKNavigation? func stopLoading() func goBack() -> WKNavigation? func goForward() -> WKNavigation?
func stringByEvaluatingJavaScriptFromString (script: String) -> String?
func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject!, NSError!) -> Void)?)
weak var delegate: UIWebViewDelegate? weak var navigationDelegate: WKNavigationDelegate?
weak var UIDelegate: WKUIDelegate?
Swift はココの戻り値の型の違いを区別してしまう! (かなり頑張ったけど無理でした笑)
手順1: WKWebView の IF を Protocol として切り出す
// WKWebViewProtocol.swift
#import <UIKit/UIKit.h> #import <WebKit/WebKit.h>
@protocol WKWebViewProtocol <NSObject>
@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack; @property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward; @property (nonatomic, readonly, getter=isLoading) BOOL loading;
@property (nonatomic, readonly) NSURL *URL; @property (nonatomic, readonly, copy) NSString *title;
- (void)loadRequest:(NSURLRequest *)request;
- (void)reload; - (void)stopLoading; - (void)goBack; - (void)goForward;
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler;
@end
手順1: WKWebView の IF を Protocol として切り出す
// WKWebViewProtocol.swift
#import <UIKit/UIKit.h> #import <WebKit/WebKit.h>
@protocol WKWebViewProtocol <NSObject>
@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack; @property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward; @property (nonatomic, readonly, getter=isLoading) BOOL loading;
@property (nonatomic, readonly) NSURL *URL; @property (nonatomic, readonly, copy) NSString *title;
- (void)loadRequest:(NSURLRequest *)request;
- (void)reload; - (void)stopLoading; - (void)goBack; - (void)goForward;
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler;
@endWKWebViewのヘッダファイルからコピペしてくる
手順2: WKWebView の拡張で Protocol に適合
// WKWebView+ProtocolConformed.h
#import "WKWebViewProtocol.h"
@interface WKWebView(YSSWebViewProtocol) <WKWebViewProtocol>
@end
// WKWebView+ProtocolConformed.m
#import "WKWebView+ProtocolConformed.h"
@implementation WKWebView(YSSWebViewProtocol)
@end
手順2: WKWebView の拡張で Protocol に適合
// WKWebView+ProtocolConformed.h
#import "WKWebViewProtocol.h"
@interface WKWebView(YSSWebViewProtocol) <WKWebViewProtocol>
@end
// WKWebView+ProtocolConformed.m
#import "WKWebView+ProtocolConformed.h"
@implementation WKWebView(YSSWebViewProtocol)
@end
Protocol の適合を宣言
手順2: WKWebView の拡張で Protocol に適合
// WKWebView+ProtocolConformed.h
#import "WKWebViewProtocol.h"
@interface WKWebView(YSSWebViewProtocol) <WKWebViewProtocol>
@end
// WKWebView+ProtocolConformed.m
#import "WKWebView+ProtocolConformed.h"
@implementation WKWebView(YSSWebViewProtocol)
@end
元から実装されているので拡張は何もいらない!
手順3: 同様に UIWebView の拡張も作成
// UIWebView+WKProtocolConformed.h
#import "WKWebViewProtocol.h"
@interface UIWebView (WKProtocolConformed) <WKWebViewProtocol>
@end
手順3: 同様に UIWebView の拡張も作成
// UIWebView+WKProtocolConformed.h
#import "WKWebViewProtocol.h"
@interface UIWebView (WKProtocolConformed) <WKWebViewProtocol>
@end 同様に WKWebView の Protocol 適合を宣言
UIWebView には足りないメソッドを実装する!
// UIWebView+WKProtocolConformed.m
#import "UIWebView+WKProtocolConformed.h"
@implementation UIWebView (WKProtocolConformed)
- (NSURL *)URL { NSString *URLString = [self stringByEvaluatingJavaScriptFromString:@"document.URL"]; return [NSURL URLWithString:URLString]; }
- (NSString *)title { return [self stringByEvaluatingJavaScriptFromString:@"document.title"]; }
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler { NSString *result = [self stringByEvaluatingJavaScriptFromString:javaScriptString]; if(completionHandler) { completionHandler(result, nil); } }
@end
UIWebView には足りないメソッドを実装する!
// UIWebView+WKProtocolConformed.m
#import "UIWebView+WKProtocolConformed.h"
@implementation UIWebView (WKProtocolConformed)
- (NSURL *)URL { NSString *URLString = [self stringByEvaluatingJavaScriptFromString:@"document.URL"]; return [NSURL URLWithString:URLString]; }
- (NSString *)title { return [self stringByEvaluatingJavaScriptFromString:@"document.title"]; }
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler { NSString *result = [self stringByEvaluatingJavaScriptFromString:javaScriptString]; if(completionHandler) { completionHandler(result, nil); } }
@end loadRequest: などは元から実装されているのでスルー!
Controller で WKWebViewProtocol 型として webView を保持// MyViewController.swift
import UIKit import WebKit
class ViewController: UIViewController { var webView: WKWebViewProtocol! override func viewDidLoad() { super.viewDidLoad() self.createWebView() } func createWebView() { if NSClassFromString("WKWebView") != nil { let wkWebView = WKWebView(frame: self.view.bounds) self.view.addSubview(wkWebView) webView = wkWebView } else { let uiWebView = UIWebView(frame: self.view.bounds) self.view.addSubview(uiWebView) webView = uiWebView as WKWebViewProtocol } }
…(続く)
Controller で WKWebViewProtocol 型として webView を保持// MyViewController.swift
import UIKit import WebKit
class ViewController: UIViewController { var webView: WKWebViewProtocol! override func viewDidLoad() { super.viewDidLoad() self.createWebView() } func createWebView() { if NSClassFromString("WKWebView") != nil { let wkWebView = WKWebView(frame: self.view.bounds) self.view.addSubview(wkWebView) webView = wkWebView } else { let uiWebView = UIWebView(frame: self.view.bounds) self.view.addSubview(uiWebView) webView = uiWebView as WKWebViewProtocol } }
…(続く)
WKWebViewProtocol! 型として1個だけ変数を保持!
Controller で WKWebViewProtocol 型として webView を保持// MyViewController.swift
import UIKit import WebKit
class ViewController: UIViewController { var webView: WKWebViewProtocol! override func viewDidLoad() { super.viewDidLoad() self.createWebView() } func createWebView() { if NSClassFromString("WKWebView") != nil { let wkWebView = WKWebView(frame: self.view.bounds) self.view.addSubview(wkWebView) webView = wkWebView } else { let uiWebView = UIWebView(frame: self.view.bounds) self.view.addSubview(uiWebView) webView = uiWebView as WKWebViewProtocol } }
…(続く)インスタンス化の後は、WK / UI 共に変数 webView に格納できる!
Controller で WKWebViewProtocol 型として webView を保持
// MyViewController.swift
…(続き)
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let URL = NSURL(string: "http://yahoo.co.jp") let req = NSURLRequest(URL: URL!) webView.loadRequest(req) } }
Controller で WKWebViewProtocol 型として webView を保持
// MyViewController.swift
…(続き)
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let URL = NSURL(string: "http://yahoo.co.jp") let req = NSURLRequest(URL: URL!) webView.loadRequest(req) } }
以後は何の区別もなく同じ webView として扱える!
方針1: Wrapper で包む
方針2: UIWebViewをいじる
• 特別なことをしていないので安心。• だいぶ裏技っぽい。 (戻り値型を捻じ曲げてる辺り特に)
• 全メソッドを実装して、それぞれ分岐処理を書くのが面倒。
• UIWebViewで足りないメソッドだけ実装すれば良い。
2014年12月 5日 Yahoo! JAPAN Tech Blog
let UIWebView as WKWebView
Thanks!
Blog: http://taketo1024.hateblo.jpTwitter: @taketo1024