[iOS 8] WKBackForwardList で 閲覧履歴を管理する

iOS8

サンプルプロジェクトを起動してみる

hirai-yuki/WebBrowserSample からサンプルプロジェクトをダウンロードして、Xcode 6 で開き、実行してみましょう(pod installを忘れずに!)。

サンプルアプリを実行すると、クラスメソッド株式会社のホームページが表示されます。

ios8-webkit-history-1

この時点では、画面下部のツールバーにある「<」と「>」が無効になっています。

ios8-webkit-history-1-2

ページ内の適当なリンクをクリックして、画面遷移してみましょう。

ios8-webkit-history-1-3

すると「<」が有効になりますね。

ios8-webkit-history-2

再度ページ内の適当なリンクをクリックして画面遷移します。

ios8-webkit-history-2-2

まだ「>」が無効です。ここで「<」をタップしてみましょう。

ios8-webkit-history-3

すると、前のページに戻り「>」も有効になりますね。

ios8-webkit-history-4

「>」をタップしてみましょう。先ほど表示していたページが表示されます。「>」は無効になりました。

ios8-webkit-history-5

「<」を長押ししてみましょう。すると、履歴一覧が表示されます。

ios8-webkit-history-6

履歴の一番下をタップしてみましょう。最初に表示していたページに戻りましたね。今度は「<」が無効になりました。

ios8-webkit-history-6-2

今度は「>」を長押ししてみましょう。履歴一覧が表示されます。

ios8-webkit-history-8

履歴の一番下をタップしてみましょう。最後に表示したページが表示されます。「>」は無効になりました。

ios8-webkit-history-8

WebKit Framework で Safari ライクな閲覧履歴管理ができるようになった

サンプルプロジェクトを実行して分かるように、WebKit Frameworkでは、Safari ライクな閲覧履歴管理が行えます。閲覧履歴は WKWebView クラスの backForwardList プロパティで取得できます。

backForwardListWKBackForwardList クラスと WKBackForwardListItem で構成されており、これらのクラスによって現在のページや、前・次のページなどへ非常に簡単にアクセスできるようになっております。

実際にどのように使っているかソースコードを見てみましょう。

「<」と「>」

「<」と「>」の有効・無効の切り替え

サンプルプロジェクトでは、画面の遷移に合わせて「<」と「>」の有効・無効が切り替わっていますね。これは、WKWebViewcanGoBack プロパティと canGoForward プロパティの変化をKVOで監視することで実現しています。

CLMWebBrowserViewController.m WKWebView クラスの canGoBack/canGoForward プロパティの変更を監視(KVO)
- (void)viewDidLoad
{
    [super viewDidLoad];

    // WKWebView インスタンスのプロパティの変更を監視する
    [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
    [self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
    [self.webView addObserver:self forKeyPath:@"loading" options:NSKeyValueObservingOptionNew context:nil];
    [self.webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
    [self.webView addObserver:self forKeyPath:@"canGoForward" options:NSKeyValueObservingOptionNew context:nil];

    // 初回画面表示時にIntialURLで指定した Web ページを読み込む
    NSURL *url = [NSURL URLWithString:InitialURL];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}

- (void)dealloc
{
    // WKWebView インスタンスのプロパティの変更を監視を解除する
    [self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
    [self.webView removeObserver:self forKeyPath:@"title"];
    [self.webView removeObserver:self forKeyPath:@"loading"];
    [self.webView removeObserver:self forKeyPath:@"canGoBack"];
    [self.webView removeObserver:self forKeyPath:@"canGoForward"];
}
CLMWebBrowserViewController.m 「<」と「>」の有効・無効の切り替えているところ
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        // estimatedProgressが変更されたら、プログレスバーを更新する
        [self.navigationController setSGProgressPercentage:self.webView.estimatedProgress * 100.0f];
    } else if ([keyPath isEqualToString:@"title"]) {
        // titleが変更されたら、ナビゲーションバーのタイトルを設定する
        self.title = self.webView.title;
    } else if ([keyPath isEqualToString:@"loading"]) {
        // loadingが変更されたら、ステータスバーのインジケーターの表示・非表示を切り替える
        [UIApplication sharedApplication].networkActivityIndicatorVisible = self.webView.loading;

        // リロードボタンと読み込み停止ボタンの有効・無効を切り替える
        self.reloadButton.enabled = !self.webView.loading;
        self.stopButton.enabled = self.webView.loading;
    } else if ([keyPath isEqualToString:@"canGoBack"]) {
        // canGoBackが変更されたら、「<」ボタンの有効・無効を切り替える
        self.backButton.enabled = self.webView.canGoBack;
    } else if ([keyPath isEqualToString:@"canGoForward"]) {
        // canGoForwardが変更されたら、「>」ボタンの有効・無効を切り替える
        self.forwardButton.enabled = self.webView.canGoForward;
    }
}

canGoBack プロパティと canGoForward プロパティは文字通り、現在のページから戻る・進むことができるかどうかをBOOLで表します。このプロパティ自体は WKWebViewで新しく追加されたものではなく、UIWebView にもありました。

UIWebView との違いは、KVOで変更を監視できることです。これを利用して「<」と「>」の有効・無効の切り替えています。

「<」と「>」のタップ時の動作

CLMWebBrowserViewController.m 「<」と「>」タップ時の動作
- (IBAction)didTapBackButton:(id)sender
{
    [self.webView goBack];
}

- (IBAction)didTapForwardButton:(id)sender
{
    [self.webView goForward];
}

「<」と「>」がタップされたら、それぞれ - goBack メソッドと - goForward メソッドを実行しています。これもUIWebViewにもあったメソッドです。これらのメソッドで「前のページに1つ戻る」や「次のページに1つ進む」を実現しているのです。

これらのメソッドでページを遷移すると、WKWebView では、canGoBack プロパティと canGoForward プロパティの値を適宜変更しています。

これにより、正しく「<」と「>」の有効・無効が設定されるようになります。

閲覧履歴一覧の表示

サンプルプロジェクトでは、「<」と「>」を長押しすることで、進むと戻るの閲覧履歴一覧を表示しています。

CLMWebBrowserViewController.m 「<」と「>」長押し時の動作
- (void)didLongPressBackButton:(UILongPressGestureRecognizer *)gesutureRecognizer
{
    if (gesutureRecognizer.state == UIGestureRecognizerStateBegan) {
        [self performSegueWithIdentifier:@"showBackListSegue" sender:self];
    }
}

- (void)didLongPressForwardButton:(UILongPressGestureRecognizer *)gesutureRecognizer
{
    if (gesutureRecognizer.state == UIGestureRecognizerStateBegan) {
        [self performSegueWithIdentifier:@"showForwardListSegue" sender:self];
    }
}

長押しされると、- performSegueWithIdentifier:で、それぞれ showBackListSegueshowForwardListSegue をセグエの ID として指定して実行しています。

閲覧履歴一覧画面は CLMBackForwardListViewController で実装しています。このビューコントローラーは、listプロパティにセットされた WKBackForwardListItem インスタンスの配列をテーブルビューで表示するだけの簡単な画面です。

- prepareForSegue: メソッドを実装して、閲覧履歴一覧画面に遷移する前に表示すべき閲覧履歴一覧を設定しています。

CLMWebBrowserViewController.m 閲覧履歴一覧画面に遷移する前の処理
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"showBackListSegue"]) {
        // 履歴画面に「戻る」履歴一覧をセットする
        UINavigationController *navigationController = (UINavigationController *)segue.destinationViewController;
        CLMBackForwardListViewController *backListViewController = navigationController.topViewController;
        backListViewController.list = [[self.webView.backForwardList.backList reverseObjectEnumerator] allObjects];
    } else if ([segue.identifier isEqualToString:@"showForwardListSegue"]) {
        // 履歴画面に「進む」履歴一覧をセットする
        UINavigationController *navigationController = (UINavigationController *)segue.destinationViewController;
        CLMBackForwardListViewController *forwardListViewController = (CLMBackForwardListViewController *)navigationController.topViewController;
        forwardListViewController.list = self.webView.backForwardList.forwardList;
    }
}

WKBackForwardList

WKWebViewbackForwardListプロパティは、WKBackForwardList クラスのインスタンスがセットされています。この WKBackForwardList クラスが WKWebView の 閲覧履歴を管理しています。

サンプルプロジェクトのように、「戻る」閲覧履歴一覧は backList プロパティ、「進む」閲覧履歴一覧は forwardList プロパティで取得できます。これらのプロパティは、WKBackForwardListItem インスタンスの配列がセットされています。

WKBackForwardListItem

WKBackForwardListItem は、URLtitleinitialURLで表示したページの情報を表現します。

CLMBackForwardListViewController では URLtitleをセルの表示項目としてセットしています。

CLMBackForwardListViewController.m 閲覧履歴一覧セルの表示
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier" forIndexPath:indexPath];

    WKBackForwardListItem *item = self.list[indexPath.row];

    cell.textLabel.text = item.title;
    cell.detailTextLabel.text = [item.URL absoluteString];

    return cell;
}

閲覧履歴からのページ遷移

閲覧履歴から遷移する Web ページを選択した場合は、WKWebView- goToBackForwardListItem: メソッドを利用して、該当する WKBackForwardListItem のインスタンスを指定します。

- (IBAction)unwindToWebBrowser:(UIStoryboardSegue *)segue
{
    if (self.backForwardListItem) {
        [self.webView goToBackForwardListItem:self.backForwardListItem];
        self.backForwardListItem = nil;
    }
}

- goToBackForwardListItem: メソッドを利用して Web ページを遷移すると、- goBack メソッドと - goForward メソッド同様、canGoBack プロパティと canGoForward プロパティの値を適宜変更しています。

まとめ

WebKit Framework では、WKBackForwardList を利用することで、簡単に Safari ライクな閲覧履歴を実現することができます。

次回は、WebKit Framework を利用して、Objective-C と JavaScriptのやりとりについて書きたいと思います。