iOS
Swift
RxSwift

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

概要

  • 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を使った実装サンプル記事を書く