PaTrash2.0を作る - その12 [プログラミング]
もうほとんどできあがったPaTrash2.0。あともうちょっとのことろで問題が出た。この問題には結構な時間を費やした。わかってしまえばたいしたことないバグなんだけど、普段アプリを書いててそれと同じようにやってると、システム環境設定ペインのようなプラグインのプログラミングには落とし穴があった。今日はその話。
詳しく見てみると英語のLocalizable.strings(Localizeする前の文字列をそのまま保持しているファイル)の中身も読み込まれていない。Mac OS XのLocalizeのメカニズムは非常にうまくできていて、プログラミングする上ではシンプルで効率的で、しかも首尾一貫している。ふつうなら慣れてしまえばあまり間違いようがない。いろいろ調べてもなんでロードされないのかなかなかわからなかった。
他を調べてみると、_windowインスタンスに乗ったビュー以外のリソースがすべてロードされずにnilになっていた。たとえばペインのAboutをシステム環境設定のシートとして追加して、そこにアイコンを表示するように書いた。アイコンはTIFFイメージのリソースでふつうやるように
あれえ、おっかしいなあ、と悩んでいて、ふと気がついた。よく考えたらこのコードが実行されるときのmainBundleはアプリそのもの、つまりシステム環境設定のバンドルになる。これはあたりまえじゃん、NSBundleを使ってプラグインを書くときに一番注意しないといけないことだった。_windowインスタンスに乗ったビューは、親クラスであるNSPreferencePaneのinitWithBundle:で読み込まれるので、Localizeしてあれば設定に従う。こっちが問題ないのも当然だった。
例えばさっきのTIFFリソースを読み込むには
Localizable.stringsに書いたリソースはふつうNSLocalzedString()で読み込むように書く。Xcodeでシンタクスカラーリングを使っていればNSLocalzedString()はマクロであることがわかるけど、この定義は NSBundle.hの中にあって
初歩的なバグだけど、プラグインを書くことは少ないので、案外気がつきにくい。今回これでかなりの時間を無駄にしてしまった。
これは結局NSPreferencePaneのヘッダファイルを「Read Class Files...」でもう一度読み込ませることで解決した。NSPreferencePaneはCocoa Frameworkの一部ではなくてInterface Builderの標準の検索パスに含まれていない。最初にXcodeでPreference Paneのテンプレートを選んだ時点でヘッダは読み込まれているはずだけど、なぜかそのパスをInterface Builderは忘れるらしい。わかればなんていうことはないけど、こういうのも困ったもんだ。
10 ほとんど完成、しかし問題発生
だいたいできあがったので、動作を確認したあと、Localize(日本語リソースを追加)した。ところがなんどやってもLocalizeした文字列が読み込まれない。環境設定ペインのビューを(前に書いた_windowインスタンスに乗った部品)Localizeしたらそれはちゃんとロードされる。しかしLocalizable.stringsに書いた日本語の文字列は読み込まれない。詳しく見てみると英語のLocalizable.strings(Localizeする前の文字列をそのまま保持しているファイル)の中身も読み込まれていない。Mac OS XのLocalizeのメカニズムは非常にうまくできていて、プログラミングする上ではシンプルで効率的で、しかも首尾一貫している。ふつうなら慣れてしまえばあまり間違いようがない。いろいろ調べてもなんでロードされないのかなかなかわからなかった。
他を調べてみると、_windowインスタンスに乗ったビュー以外のリソースがすべてロードされずにnilになっていた。たとえばペインのAboutをシステム環境設定のシートとして追加して、そこにアイコンを表示するように書いた。アイコンはTIFFイメージのリソースでふつうやるように
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"PaTrImage" ofType:@"tif"]; NSImage *imageObj = [[NSImage alloc] initWithContentsOfFile:imagePath]; [NSApp beginSheet:aboutSheet modalForWindow:[NSApp mainWindow] modalDelegate:self didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) contextInfo:nil]; [patrashImage setImage:imageObj];みたいにした。ようするにバンドルリソースから読み込んでシートに貼付けたからっぽのビューにsetImageする、ということをした。ところがこのTIFFリソースのパスにnilが返る。
あれえ、おっかしいなあ、と悩んでいて、ふと気がついた。よく考えたらこのコードが実行されるときのmainBundleはアプリそのもの、つまりシステム環境設定のバンドルになる。これはあたりまえじゃん、NSBundleを使ってプラグインを書くときに一番注意しないといけないことだった。_windowインスタンスに乗ったビューは、親クラスであるNSPreferencePaneのinitWithBundle:で読み込まれるので、Localizeしてあれば設定に従う。こっちが問題ないのも当然だった。
例えばさっきのTIFFリソースを読み込むには
NSBundle *myBundle = [NSBundle bundleForClass:[self class]]; NSString *imagePath = [myBundle pathForResource:@"PaTrImage" ofType:@"tif"]; NSImage *imageObj = [[NSImage alloc] initWithContentsOfFile:imagePath];としないといけない。
Localizable.stringsに書いたリソースはふつうNSLocalzedString()で読み込むように書く。Xcodeでシンタクスカラーリングを使っていればNSLocalzedString()はマクロであることがわかるけど、この定義は NSBundle.hの中にあって
#define NSLocalizedString(key, comment) \ [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]となっている。この場合もシステム環境設定のバンドルリソースを検索することになってしまう。環境設定ペインのようなプラグインの場合は
NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)などのBundleを指定するマクロを使うか、あるいは
#define MyLocalizedString(key, comment) \ [[NSBundle bundleForClass:[self class]] \ localizedStringForKey:(key) \ value:@"" \ table:nil]としてNSLocalzedString()のかわりに使う、ということをしないといけない。
初歩的なバグだけど、プラグインを書くことは少ないので、案外気がつきにくい。今回これでかなりの時間を無駄にしてしまった。
10.1 おまけ
うれしいおまけではない。何かの拍子にInterface Builderでアウトレットが表示されなくなった。NSPereferencePaneのサブクラスを作って主要クラスにするわけだけど、NSPereferencePaneの@privateなアウトレットがでてこない。Interface Builderでは_windowアウトレットがみつからない、なんていうエラーメッセージが出る。これは結局NSPreferencePaneのヘッダファイルを「Read Class Files...」でもう一度読み込ませることで解決した。NSPreferencePaneはCocoa Frameworkの一部ではなくてInterface Builderの標準の検索パスに含まれていない。最初にXcodeでPreference Paneのテンプレートを選んだ時点でヘッダは読み込まれているはずだけど、なぜかそのパスをInterface Builderは忘れるらしい。わかればなんていうことはないけど、こういうのも困ったもんだ。
2012-08-18 21:40
nice!(0)
コメント(0)
トラックバック(0)
コメント 0