LoginSignup
9
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-08-20

概要

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5