Edited at

RxSwiftを使ってWKWebViewのローディング処理をイイ感じにする

More than 1 year has passed since last update.


概要


  • WKWebViewのローディング処理(プログレスバーのUI更新やNavigationBarのTitleの変更などをイイ感じにRxSwiftで書く


イメージ

rsw.gif


サンプルリポジトリ


環境


  • 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

スクリーンショット 2018-08-21 1.13.51.png
スクリーンショット 2018-08-21 1.14.01.png


  • ボタンをタップしたら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の表示・非表示



  • ロードが終わると、NavigationControllertitleに表示された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)
}
}



  • 仕様は全く同じ


良いところ


  • 読みやすい :tada:

  • 処理が簡潔


  • removeObserver を気にしなくてもよい


悪いところ


  • key名がまだベタ書きになっている

  • (少しずれているが、)習得コストが高い


まとめ


  • RxSwiftを使うことでコードが読みやすく、実装が簡潔になった

  • めんどくさいremoveObserver を気にしなくても良くなった

  • 習得コストが高い


所感


  • 別にRxSwiftを使わなくても実装はできる。

  • が、人間は忘れます。 いまはダイジョブでもremoveObserverを忘れるときが絶対来ます


    • そのときハマらないために、RxSwiftを使った書き方を覚えておきたいですね


      • RxSwiftでまずハマりそうなきがしたがそのときはがんばろう






次にやりたい



  • RxWebKit というライブラリがあり、もっと簡潔にかけそうだったので RxWebKitを使った実装サンプル記事を書く