2010-12-11

Cocoa アプリに JavaScript のライブラリを組み込む - Xcode の使い方

MacBloggerGlass に google-code-prettify を組み込んだ。今回はその手順を説明する。概略は次の通り。

  1. JavaScript ライブラリをプロジェクトのリソースとして追加する。
  2. JavaScript ファイルをコンパイル対象から外す。
  3. アプリバンドルへのファイルコピーのためのビルドフェーズを追加する。

今回の手順は、ビルドにより作られるアプリバンドルを以下のような構造にするためのものだ。また、Xcode でプロジェクトウィンドウの「グループとファイル」で見たときにも lib/prettify という構造に見え、かつ Finder から見えるフォルダ構造(つまりファイルシステム上の構造)も同じになるようにしている。Xcode では、プロジェクトのグループ構造が実際のフォルダ構造と同じである必要はないし、アプリバンドルの構造と一致させる必要もない。しかし、同じにしておく方がわかりやすい。むしろ、異なる構造にしておくと間違いの元だろう。

MacBloggerGlass.app/
+-- Contents/
    +-- Info.plist
    +-- MacOS/
    |   +-- ...
    +-- PkgInfo
    +-- Resources/
        +-- English.lproj/
        |   +-- ...
        +-- template.html
        +-- default.css
        +-- lib/
            +-- prettify/
                +-- ...
                +-- prettify.js
                +-- prettify.css

ちなみに、以下が現在のプロトタイプ #0 のスクリーンショット。google-code-prettify を組み込んだことによりソースコードが色付けされていることがわかる。

MacBloggerGlass プロトタイプ #0
アプリウィンドウ
google-code-prettify を組み込んだ。

JavaScript ライブラリをプロジェクトのリソースとして追加する

google-code-prettify は以下のように ~/tmp の下の lib フォルダに置いてあるものとする。この lib 以下をプロジェクトに追加することになる。

~/tmp/lib/
+-- prettify/
    +-- ...
    +-- prettify.js
    +-- prettify.css

まず、プロジェクトウィンドウの「グループとファイル」から Resources を選択した状態で、メニューより「プロジェクト」>「プロジェクトに追加...」を実行する。ファイル選択シートが現れるので、上記の lib フォルダを選択し「追加」ボタンを押す。

ここで、さらにオプションを選択するシートが現れるが、そこでは右のスクリーンショットのように選ぶ。これにより、プロジェクトのフォルダ内に lib フォルダ、さらにその中に prettify フォルダが作られ、ライブラリのファイルがコピーされている。

また、同時に「グループとファイル」では Resource の中に lib グループ、その中に prettify グループが作られている(右のスクリーンショットを参照)。

JavaScript ファイルをコンパイル対象から外す

Xcode のデフォルトルールでは、JavaScript ファイルはコンパイルが必要な「ソースファイル」の一種に設定されている。このため、JavaScript ファイルをプロジェクトに追加すると、ビルドフェーズの「ソースをコンパイル」に追加されてしまう(右のスクリーンショット参照)。

このままでもビルド時に warning が出るだけで実害はないけれど、大量の黄色の三角を見るのは気に入らない。この warning を消すには「ソースをコンパイル」から JavaScript ファイルを削除すれば良い。具体的には「グループとファイル」から「ターゲット」>「(アプリ名).app」>「ソースをコンパイル」を開き(右のスクリーンショットの状態になる)、js ファイルをすべて選択して削除すれば良い。ここに並んでいるのはファイルへの参照だけなので、ここで削除してもプロジェクトに追加したファイル自体が(プロジェクトから)消えることはない。

もう一つ。google-code-prettify には prettify.css という CSS ファイルがふくまれている。Xcode のデフォルトルールでは CSS ファイルはコピーされるべきバンドルリソースになっており、ビルドフェーズの「バンドルリソースをコピー」に追加される。このままでは適切な場所((アプリ名).app/Contents/Resources/lib/prettify/)にコピーされないので、ここからは削除しておく。手順は js ファイルの場合と同様。

アプリバンドルへのファイルコピーのためのビルドフェーズを追加する

最後のステップは、ビルドによって作られるアプリバンドルに JavaScript ライブラリが構造を保ったままコピーされるようにすることだ。これには、ファイルコピーのためのビルドフェーズを新たに追加すれば良い。

具体的には、メニューから「プロジェクト」>「新規ビルドフェーズ」>「新規コピーファイル」を実行する。右のスクリーンショットのようなダイアログ(インスペクタのパネル)が現れるので、「デスティネーション」として「リソース」を選び(デフォルトでそうなっている)、「パス」に lib/prettify を入力する。これは、アプリバンドルのリソースフォルダ中に lib/prettify を作って対象ファイルをコピーする、というビルドフェーズを意味する。

後は、このビルドフェーズに Resouces/lib/prettify の下にあるファイルをすべて追加するだけ(選択してドラッグ&ドロップ)。右がその結果だ。ビルドフェーズの名前もわかりやすいものに変更している。

確認には、一度、クリーンしてからビルドを行い、Finder から build/Debug (または build/Release) の下にできているアプリバンドル(アプリ名.app)の内容を調べる。つまり、ポップアップメニューから「パッケージの内容を表示」でアプリバンドルを開き、Contents、Resources とたどり、そこに lib があること、またその中に prettify が、さらにその中には js ファイル(と 1 つの CSS ファイル)があることを確かめる。

おまけ

前回の記事(→「内部リンクの置き換えを実現する」)の HTML テンプレートと CSS ファイルの下りで、WebView にわたすベース URL について少し書いた。そこではアプリバンドルのトップの位置を(ファイル URL の形で)ベース URL に指定していた。このため、テンプレート中で CSS ファイルをリンクするために href='Contents/Resources/default.css' と書く必要があった。しかし、前回の記事中にも書いたように、これはベース URL の位置が適切とは言えない。Contents/Resources がベースになるべきだろう。

NSBundle のリファレンスを探してみたが、都合良く Contents/Resources をパスなり URL なりで取り出すメソッドは見つからなかった。そこで考えたのは、Contents/Resources をベースと考えるのではく、template.html の位置をベースとするというものだ。

追記@2010-12-12

上記の「都合良く Contents/Resources をパスなり URL なりで取り出すメソッド」は存在していて(resourcePath と resourceURL)、見つけられなかったのは、単にわたしの目がフシアナだっただけ。別件で「Objective-C逆引きハンドブック」を調べていて見つけた。「NSBundle Class Reference」を見直したら、そこにもちゃんと書かれていた。

このメソッドを使うなら、以下に書いた baseURL メソッドはこう書ける。

- (NSURL *)baseURL {
    return [[NSBundle mainBundle] resourceURL];
}

そうだよなあ。ないはずないよなあ。昨日のわたしはどんな目をしてたんだろう。(´・ω・`)

Objective-C逆引きハンドブック
林 晃
シーアンドアール研究所 ( 2010-02-26 )
ISBN: 9784863540514

具体的には NSBundle のメソッドで template.html の位置をファイル URL として取得し、パスのコンポーネントに分解。最後の要素(template.html そのもの)を取り除いた上で、残った要素からファイル URL を組み立てる。これで template.html が置かれたフォルダのファイル URL が出来上がる。

該当する部分のコードを以下に示す(AppController.m より)。

- (NSURL *)fileURLForHTMLTemplate {
    NSBundle *mainBundle = [NSBundle mainBundle];
    return [mainBundle URLForResource:BGHTMLTemplateName withExtension:@"html"];
}

- (NSURL *)baseURL {
    // the base URL must be identical to the path of the HTML template.
    NSMutableArray *components = 
    [NSMutableArray
     arrayWithArray:[[self fileURLForHTMLTemplate] pathComponents]];

    [components removeLastObject]; // remove the template name

    return [NSURL fileURLWithPathComponents:components];
}
@end

ただし、この方法では、template.html がローカライズ対象となった場合に他のリソース(CSS や JavaScript)もローカライズ対象にしなければならない。CSS ならフォントの指定あたりで一部ローカライズすることもあるだろうが、さすがに JavaScript をローカライズすることはないと思う。ローカライズ対象に指定したところで English.lproj とか Japanese.lproj にコピーされるだけのことだが、少しムダかなと言う気にはなる。

今回は template.html をベース URL のための目印に使ったが、ローカライズが絡む場合は別の目印を使うか、ビルドフェーズでアプリバンドル内にシンボリックリンクを張るなどの工夫が必要になるかもしれない。

関連リンク

関連記事

0 件のコメント:

コメントを投稿