履歴メニューの追加

履歴メニューの追加

次は、履歴メニューの追加だ。いままで訪れたページの一覧を表示して、選択すると戻ることができるメニューだ。

8.1 WebHistory

実は、WebKit 自体が履歴の機能をサポートしているんだ。WebView で新しいペー ジを訪れると、自動的に履歴のリストが更新される。だからそのリストを取り出して、 更新された部分をメニューに付け加えるだけでいい。

履歴をサポートするためのクラスは、WebHistory だ。このクラスのインスタンス を作ることで、履歴の記録が開始される。

webHistory.jpg


8.1.1 インスタンスの作成、共有

ただし、WebHistory のインスタンスはユーザが自分で作らないといけない。 alloc、init でインスタンスを作成して、setOptionalSharedHistory: を使っ て、それを共有させるんだ。ちょっと特殊な共有オブジェクトの使い方だね。

WebKit/WebHistory.h

+ (void)setOptionalSharedHistory:(WebHistory*)history;

だから、初期化メソッドで、次のようなソースコードを書く必要がある。

(sample)

{
  ...
  // WebHistory のインスタンスを作成します
  WebHistory* history;
  history = [[WebHistory alloc] init];

  // WebHistory オブジェクトを共有化します
  [WebHistory setOptionalSharedHistory:history];


8.1.2 ノーテフィケーション

また、WebHistory は履歴が更新されるとノーテフィケーションを投げる。その通 知を受け取るために、NSNotificationCenter に登録をしておこう。ここでは、履歴が 追加された時に呼ばれる WebHistoryItemsAddedNotification と、履歴が削除され たときのための WebHistoryAllItemsRemovedNotification を登録しておく。


8.2 WebHistoryItem

それぞれの履歴は、WebHistoryItem っていうクラスで表現されるんだ。

webHistoryItem.jpg
図8-2 WebHistoryItem クラス階層

WebHistoryItem からは、URL やページのタイトル、アイコンなどが取得できる。

WebKit/WebHistoryItem.h

- (NSString*)URLString;

- (NSString*)title;

- (NSImage*)icon;

ただし、使い方がちょっと難しい。どうやら、WebHistoryItem は新しいページに 移動したときに作られるらしい。この段階ではまだページのタイトルが読み込まれてい ないので、title メソッドを呼んでもnil が返ってくるんだ。ページが読み込まれて、 タイトルが取得されたら、WebHistoryItem もタイトルを返すようになる。ここんとこ、注意。


8.3 実装

ということで、履歴を追加してみよう。前のプロジェクトでGo メニューを追加し たけど、その下に付け加えることにしよう。まず、「Clear History」っていうメ ニューアイテムを追加する。これが選択されたら、履歴を消去する。そして、その下に どんどん履歴を追加していくんだ。

■履歴メニューの追加

この履歴の管理をするために、MainMenu.nib に「Controller」クラスを追加する ことにしよう。こいつがWebHistory のインスタンスの作成、ノーティフィケーショ ンの受信、メニューの更新とかを受け持つことにする。

1. まずメニューを変更するために、MainMenu.nib を開く。MainMenu.nib をダブル クリックして、Interface Builder に移動してくれ。

2. MainMenu のGo メニューに「Clear History」を追加する。こんな感じにしてくれ。


goMenu-1.jpg
図8-3 Go メニュー

3. Controller クラスを作る。Classes タブに移動して、NSObject のサブクラスを作ってくれ。「Controller」という名前にする。

4. Controller にアウトレットとアクションを追加する。アウトレットは Go メニュー のための goMenu と、履歴アイテムを追加する場所を指定するための historySeparatorItem。アクションは、Clear History を受け付けるためのclearHistory: だ。

outlets-1.jpg
図8-4 Controller のアウトレット

actions-2.jpg
図8-5 Controller のアクション

5. Controller をインスタンス化する。Controller クラスを選択して、「Classes」→ 「Instansiate Controller」メニューを選んでくれ。これで、Instances タブに Controller のインスタンスができるよ。

6. Instances タブに戻って、アウトレットとアクションを接続する。まず、 Controller のアウトレットを接続。Controller を選択して、Ctrl キーを押しながらド ラッグすると、線が伸びてくる。この線を、Go メニューと、Go メニューの一番下の セパレータに接続する。それぞれアウトレットを「Connect」しよう。

Go メニューに接続するときは、下にメニューアイテムが表示されている状態で接続し てくれ。この場合だと NSMenu として接続できる。NSMenuItem として接続したい ときは、一回メニューをクリックして、メニューを隠してから接続する。

続いて、アクションの接続。追加したClear History からCtrl を押しながらドラッ グして、Controller につないでくれ。対応するアクションとして clearHistory: を選択する。

あと、Controller をNSApplcation のデリゲートにしておいてくれ。File's Owner を選択して、Ctrl キーを押しながら Controller にドラッグする。そして、delegateとして接続しおくんだ。

7. Controller のためのファイルを作る。Classes タブに戻って、Controller クラスを 選択してくれ。「Classes」→「Create Files for Controller」メニューを選んで、 Controller.h と Controller.m っていうファイルを作る。

ここまでできたら、Project Builder に戻ろう。

■ソースコードの編集

では、ソースコードを書こう。ここでは、Controller クラスの実装と、 MyDocument の簡単な変更が必要なるんだ。

8. Controller.h を変更する。WebHistoryItem の説明で書いたように、履歴からタイ トルを取得するタイミングは、ちょっといやらしい。そこで、いったん追加された履歴 を保存しておいて、後でタイトルを更新するようにしているんだ。そのために、インス タンス変数を追加しておく。配列である、
suspendedHistoryItems だ。

MyFirstBrowser/Controller.h

@interface Controller : NSObject
{
  IBOutlet id goMenu;
  IBOutlet id historySeparatorItem;

  // 更新途中の HistoryItems を保持します
  NSMutableArray*  suspendedHistoryItems;
}

9. Controller.m を編集する。けっこう長いので、ここではポイントを絞って説明しよ う。完全な実装は、ソースコードの方を見てくれ。


◆WebHistory の設定

まず、初期化のメソッドで、WebHistory のための設定をする。ここでは NSApplication のデリゲートメソッドである、applicationDidFinishLaunching: を使おう。まず、WebHistory のインスタンスを作って、共有するために登録する。 次に、WebHistory からのノーティフィケーションを受け取るための登録をするんだ。

MyFirstBrowser/Controller.m

- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
  // WebHistory のインスタンスを作成します
  WebHistory*history;
  history = [[WebHistory alloc] init];

  // WebHistory オブジェクトを共有化します
  [WebHistory setOptionalSharedHistory:history];

  // ノーティフィケーションを登録します
  NSNotificationCenter*  center;
  center = [NSNotificationCenter defaultCenter];

  [center addObserver:self
    selector:@selector(historyAllItemsRemoved:)
    name:WebHistoryAllItemsRemovedNotification
    object:history];
[center addObserver:self
    selector:@selector(historyItemsAdded:)
    name:WebHistoryItemsAddedNotification
    object:history];
}


◆履歴の追加

WebHistory に履歴が追加されると、登録したメソッド historyItemsAdded: が 呼び出される。ここで、追加された WebHistoryItem を取り出して、メニューに追加 するんだ。WebHistoryItem は、ノーティフィケーションの userInfo から取り出すことができる。

MyFirstBrowser/Controller.m

- (void)historyItemsAdded:(NSNotification*)notification
{
  // WebHistroyItem を取得します
  NSArray*     items;
  NSEnumerator*  enumerator;
  WebHistoryItem* item;

  items = [[notification userInfo] objectForKey:WebHistoryItemsKey];
  enumerator = [items objectEnumerator];

  // WebHistroyItem を追加します
  while (item = [enumerator nextObject]) {
    [self _addMenuItemForHistoryItem:item];
  }
}

実際に、メニューに追加するのは、_addMenuItemForHistoryItem: っていうメ ソッドの中だ。ここでメニューを作っているんだけど、実はこのタイミングでは、まだ WebHistoryItem のタイトルを知ることができないんだ。メニューにURL を入れて もいいんだけど、それだと無駄に長くなってしまう。だから、インスタンス変数 suspendedHistoryItems にいったん WebHistoryItem を入れておいて、また別の タイミングで更新するようにしている。

追加した履歴メニューが選択されたときは、メソッド _goToHistoryItem: を呼び 出すように設定しておく。このメソッドの中では、MyDocument の、これから追加す るメソッドを呼び出すようにしているんだ。そこで、選択したURL に飛ぶようにしている。

10. MyDocument.m を変更する。今回は2 つメソッドを追加した。1 つは、指定し たWebHistoryItem のURL に飛ぶための goToHistoryItem: だ。 WebHistoryItem からURL を取り出して、そこに移動する。

MyFirstBrowser/MyDocument.m

- (void)goToHistoryItem:(WebHistoryItem*)historyItem
{
  // URL を文字列型で取り出します
  NSString* urlString;
  urlString = [historyItem URLString];

  // NSURL を作成します
  NSURL* url;
  url = [NSURL URLWithString:urlString];

  // NSURLRequest を作成します
  NSURLRequest* request;
  request = [NSURLRequest requestWithURL:url];

  // メインフレームで、URL を開きます
  [[webView mainFrame] loadRequest:request];
}

もう1つは、WebFrameLoadDelegate のメソッドになる webView:didFinishLoadForFrame:。これは、ページが読み込み終わった時点で呼 ばれる。ここで何をするかというと、履歴メニューの更新をするんだ。この時点なら、 WebHistoryItem のタイトルが取れているはずだ。Controllerの didFnishLoadForFrame: っていうメソッドを呼び出しているんだ。

MyFirstBrowser/MyDocument.m

- (void)webView:(WebView*)sender
      didFinishLoadForFrame:(WebFrame*)frame
{
  // Controller に通知します
  [[NSApp delegate] didFinishLoadForFrame:frame];
}

あとの細かい実装は、ソースコードを見てくれ。

11. ビルドして実行する。履歴メニューの動きを確認してくれ。

browser-4.jpg
図8-6 MyFirstBrowser5 動作図

だけど、なんかアイコンがうまく表示されないんだよな。なんでだろ?

履歴を記録する機能自体は WebKit に含まれているんだけど、いざ使うとなるとけっこう面倒くさい。アプリケーションで必要な履歴機能の仕様を確認しながら、注意して使ってみてくれ。


■ここまでのプロジェクト:
MyFirstBrowser5.zip