概要
- WKWebViewのローディング処理(プログレスバーのUI更新やNavigationBarのTitleの変更などをイイ感じにRxSwiftで書く
イメージ
サンプルリポジトリ
環境
- Xcode 9.4
- Swift 4.1
- RxSwift 4.2
- RxCocoa 4.2
- RxOptional 3.5.0
環境構築
Podfile
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxOptional'
画面
ViewController | WKWebViewを内包したViewController |
---|---|
![]() |
![]() |
- ボタンをタップしたらWKWebViewを内包したViewControllerに遷移する
実装していく
まずは、Swiftのobserverを使ったパターンで実装
WKWebViewController.swift
import UIKit
import WebKit
class WKWebViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
@IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
}
private func setupWebView() {
webView.addObserver(self, forKeyPath: "loading", options: .new, context: nil)
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
let url = URL(string: "https://www.google.com/")
let urlRequest = URLRequest(url: url!)
webView.load(urlRequest)
progressView.setProgress(0.1, animated: true)
}
deinit {
webView?.removeObserver(self, forKeyPath: "loading")
webView?.removeObserver(self, forKeyPath: "estimatedProgress")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "loading" {
UIApplication.shared.isNetworkActivityIndicatorVisible = webView.isLoading
if !webView.isLoading {
progressView.setProgress(0.0, animated: false)
navigationItem.title = webView.title
}
}
if keyPath == "estimatedProgress" {
progressView.setProgress(Float(webView.estimatedProgress), animated: true)
}
}
}
- 画面を開くと
google.com
がロードされる - ロードにあわせて、以下の値が変化する
- プログレスバーの値
- プログレスバーの表示・非表示
-
ActivityIndicator
の表示・非表示
- ロードが終わると、
NavigationController
のtitle
に表示されたURLのタイトルが設定される
この書き方の良いところ
- なし
この書き方の悪いところ
-
"loading"
,"estimatedProgress"
のように、keyをベタ書きしないといけない- typoしてもコンパイルエラーで見つけることができない
- deinit時に
removeObserver
しないといけない- しないと強制終了の可能性がある
- コードが読みにくい
では、これを踏まえてRxSwiftを使ったパターンで実装
RxWKWebViewController.swift
import UIKit
import WebKit
import RxSwift
import RxCocoa
import RxOptional
class RxWKWebViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
@IBOutlet weak var progressView: UIProgressView!
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
}
private func setupWebView() {
// プログレスバーの表示制御、ゲージ制御、アクティビティインジケータ表示制御で使うため、一旦オブザーバを定義
let loadingObservable = webView.rx.observe(Bool.self, "loading")
.filterNil()
.share()
// プログレスバーの表示・非表示
loadingObservable
.map { return !$0 }
.observeOn(MainScheduler.instance)
.bind(to: progressView.rx.isHidden)
.disposed(by: disposeBag)
// iPhoneの上部の時計のところのバーの(名称不明)アクティビティインジケータ表示制御
loadingObservable
.bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
.disposed(by: disposeBag)
// NavigationControllerのタイトル表示
loadingObservable
.map { [weak self] _ in return self?.webView.title }
.observeOn(MainScheduler.instance)
.bind(to: navigationItem.rx.title)
.disposed(by: disposeBag)
// プログレスバーのゲージ制御
webView.rx.observe(Double.self, "estimatedProgress")
.filterNil()
.map { return Float($0) }
.observeOn(MainScheduler.instance)
.bind(to: progressView.rx.progress)
.disposed(by: disposeBag)
let url = URL(string: "https://www.google.com/")
let urlRequest = URLRequest(url: url!)
webView.load(urlRequest)
}
}
- 仕様は全く同じ
良いところ
- 読みやすい
- 処理が簡潔
-
removeObserver
を気にしなくてもよい
悪いところ
- key名がまだベタ書きになっている
- (少しずれているが、)習得コストが高い
まとめ
- RxSwiftを使うことでコードが読みやすく、実装が簡潔になった
- めんどくさい
removeObserver
を気にしなくても良くなった - 習得コストが高い
所感
- 別にRxSwiftを使わなくても実装はできる。
- が、人間は忘れます。 いまはダイジョブでも
removeObserver
を忘れるときが絶対来ます- そのときハマらないために、RxSwiftを使った書き方を覚えておきたいですね
- RxSwiftでまずハマりそうなきがしたがそのときはがんばろう
- そのときハマらないために、RxSwiftを使った書き方を覚えておきたいですね
次にやりたい
-
RxWebKit
というライブラリがあり、もっと簡潔にかけそうだったので RxWebKitを使った実装サンプル記事を書く