Advertisement
Advertisement

More Related Content

Advertisement

More from Taketo Sano(20)

Recently uploaded(20)

Advertisement

let UIWebView as WKWebView

  1. let UIWebView as WKWebView @taketo1024 2015/02/14 iOSオールスターズ勉強会
  2. http://maths4pg.connpass.com
  3.  本題
  4. from: WWDC2014 Introducing the Modern WebKit API
  5. from: WWDC2014 Introducing the Modern WebKit API • iOS 2.0 からお馴染み • JavaScript が Safari より遅い
  6. from: WWDC2014 Introducing the Modern WebKit API • iOS 2.0 からお馴染み • JavaScript が Safari より遅い • iOS 8.0 から導入 • Safari と同じ JavaScript エンジン! • ページ遷移ジェスチャーにも対応 • その他も何かと高性能で良い
  7. WKWebViewを使いたい!
  8. でもまだ iOS 7 を切るわけには行かない…
  9. 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? インターフェースは似ているが互換性はない
  10. 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 にしかないプロパティ
  11. 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? 型の戻り値がある
  12. 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 はコールバック形式
  13. 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もだいぶ違う)
  14. 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) } } ナイーブな下位互換対応をすると…
  15. 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 の両方を変数で宣言
  16. 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) } } ナイーブな下位互換対応をすると… 対応/非対応に応じて一方にインスタンスを入れる
  17. 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度書くことに…
  18. これはよくない
  19. バージョン分岐だらけのコードは危険 • コントローラやモデルの本来の役割が見えにくくなる。 • 古いコードが山積するといずれ誰も手に負えなくなる。 • 下位バージョンのサポートを切るときにも不必要なコー ドが残りがち。
  20. 「OSの最新バージョンだけサポートしてればいいじゃん」
  21. 2015/02 時点でも iOS 8 のシェアは 72%。 100万 ユーザいれば 28万 は iOS 7 以前。 大きなユーザ数を持つサービスには重大な問題。 As measured by the App Store on February 2, 2015.
  22. どうするか?
  23. 「複雑性保存の法則」 タスクの複雑性はある点を超えて 減らすことはできない。 移動のみ可能である。 ラリー・テスラー
 インタラクションデザイナ ソフトウェアの設計についてもあてはまる!
  24. 下位互換対応は「隠 」せよ • コントローラやモデルなどの上位層からはバージョ ン分岐を意識しなくていいようにする。 • 混み入った分岐はどこかにまとめておいて、いつで も簡単に切り離せるようにしておく。 • 広範囲で使用されるものでも、依存性は最小限に。
  25. 2014-01-15 @ potatotips 3 Method Swizzling で iOS 7 コードを iOS 6 でも動かす
  26. 今回のお題 UIWebViewと WKWebViewの
 分岐処理を隠蔽せよ
  27. 方針1: ラッパーで包む IF は WKWebView と共通にする WKWebView UIWebView 前処理 iOS 8 以上 iOS 7 以下 WKWebViewWrapper : UIView
  28. 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 } } 内部の実装イメージ
  29. 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 の両方を変数で宣言
  30. 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 } } 内部の実装イメージ 対応/非対応に応じて一方にインスタンスを入れる
  31. 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 } } 内部の実装イメージ すべてのメソッドで分岐処理
  32. 目的は達成できているのだが、 全メソッドに分岐を入れるのが あまりエレガントな感じしない。
  33. 方針2: UIWebView に WKWebView のフリをさせる
  34. WKWebView UIWebView iOS 8 以上 iOS 7 以下
  35. WKWebView UIWebView iOS 8 以上 iOS 7 以下
  36. WKWebView UIWebView WKWebView の IF iOS 8 以上 iOS 7 以下
  37. WKWebView UIWebView WKWebView の IF Controller iOS 8 以上 iOS 7 以下 外からは同じ型に見える
  38. Obj-C 先輩の力を借りる えっ
  39. 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?
  40. 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 はココの戻り値の型の違いを区別してしまう! (かなり頑張ったけど無理でした笑)
  41. Obj-C の緩さに甘える (どうせ隠 するんだしいいでしょ) …許す
  42. 手順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
  43. 手順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 WKWebViewのヘッダファイルからコピペしてくる
  44. 手順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
  45. 手順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 の適合を宣言
  46. 手順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 元から実装されているので拡張は何もいらない!
  47. 手順3: 同様に UIWebView の拡張も作成 // UIWebView+WKProtocolConformed.h #import "WKWebViewProtocol.h" @interface UIWebView (WKProtocolConformed) <WKWebViewProtocol> @end
  48. 手順3: 同様に UIWebView の拡張も作成 // UIWebView+WKProtocolConformed.h #import "WKWebViewProtocol.h" @interface UIWebView (WKProtocolConformed) <WKWebViewProtocol> @end 同様に WKWebView の Protocol 適合を宣言
  49. 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
  50. 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: などは元から実装されているのでスルー!
  51. 準備はここまで (Obj-Cのことはもう忘れてOK)
  52. 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 } } …(続く)
  53. 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個だけ変数を保持!
  54. 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 に格納できる!
  55. 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) } }
  56. 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 として扱える!
  57. DEMO
  58. 方針1: Wrapper で包む 方針2: UIWebViewをいじる • 特別なことをしていないので安心。 • だいぶ裏技っぽい。 (戻り値型を捻じ曲げてる辺り特に) • 全メソッドを実装して、それぞれ分岐 処理を書くのが面倒。 • UIWebViewで足りないメソッドだけ実 装すれば良い。
  59. いつか iOS 7 のサポートを切るとき、 UIWebView と気持ち良く別れられるように。
  60. 2014年12月 5日 Yahoo! JAPAN Tech Blog let UIWebView as WKWebView
  61. Thanks! Blog: http://taketo1024.hateblo.jp Twitter: @taketo1024
Advertisement