2013年01月04日, 編集履歴
ステータスバーアプリケーションについて
ステータスバーとは、OS Xの画面上部に表示されるメニューバーの、右側に位置する部分である。メニューバーの右端には通知やSpotlightのアイコン、時計表示、システムによって制御される機能(音量やBluetooth等)のアイコン(メニューエクストラ)が並び、その次に様々なアプリケーションのアイコンが並んでいる。
上記画像で言えば、Google NotifierやDropbox、Adium、ClamXavのアイコンがある赤く塗った領域がステータスバーである。アプリケーション自身が非アクティヴであるときやウィンドウ等を表示していないときも、状態を示したり何らかの機能を呼び出したりする場合にステータスバーにアイコン等の項目(ステータス項目)を配置することができる。
ここで言う「ステータスバーアプリケーション」とはいわゆる「常駐アプリケーション」と呼ばれるものの一種である。アプリケーションを起動してもウィンドウやDockのアイコンが表示されず、代わりにステータスバーにテキストやアイコンを表示し、そこから呼び出されるメニューやウィンドウを用いる。基本的にはバックグラウンドで常に起動しておき、タイマや何らかのイヴェントをフックして動作するアプリケーションを想定する。
ステータスバーアプリケーションの特徴は、
- 起動してもDockにアイコンが表示されない
- ステータスバーにテキストやアイコンを表示する
- ステータスバーの項目をクリックすることでメニューを表示する
ここでは上記のような特徴を持つアプリケーションを作成する。今回作成したプロジェクトはGenjiApp/StatusBarApp - GitHubで公開するので参照のこと。なおXcodeのヴァージョンは4.5.2を用いた。
Xcodeプロジェクトの作成
Xcodeを起動し、New Project…からOS X ApplicationのCocoa Applicationを選択する。ここではProject NameをStatusBarAppとし、Create Document-Based Applicationはオフに、Use Automatic Reference Countingをオンにしてプロジェクトを作成した。
常駐アプリケーションとして設定する
常駐アプリケーションとして設定するには以下の手順を行う。
- Xcodeウィンドウ左のProject Navigatorからプロジェクトを選択し、Project Editorを表示させる
- Project EditorのTARGETSからアプリケーションのターゲットを選択する
- Project EditorのInfoペインを表示させる
- Custom OS X Application Target PropertiesからApplication is agent (UIElement) Keyを追加し、TypeをBoolean、ValueをYESに設定する。
この設定により、アプリケーションは常駐アプリケーションとして起動され、Dockにアイコンが表示されなくなる。同時に、ふつうのアプリケーションであればアクティヴ時にメニューバーに各種メニュー項目が表示されるが、それも表示されなくなり、Command + Option + Escapeで呼び出される「アプリケーションの強制終了」ダイアログにもアプリケーションが表示されなくなる。したがって、後述するメニュー等でアプリケーションを終了する手段を提供しておかないと、アクティビティモニタを使用しないとアプリケーションを終了するできなくなってしまう。
メニューの追加
MainMenu.xib
にステータスバー項目をクリックしたときに表示されるメニューを追加する。
ウィンドウオブジェクトの削除
MainMenu.xib
を開く。XIBファイルにはウィンドウオブジェクトがトップレヴェルに存在しており、アプリケーション起動時にそのウィンドウが自動的に開かれてしまう。今回はウィンドウは必要ないのでこのオブジェクトは削除しておく。ウィンドウオブジェクトを削除したらAppDelegate.h
を開き、
@property (assign) IBOutlet NSWindow *window;
の行を削除する。
後々、環境設定ウィンドウ等を作成するのであれば、このウィンドウオブジェクトをとっておいて流用してもよい。その場合は起動時にウィンドウが開かないように、
- ウィンドウオブジェクトを選択する
- UtilitiesエリアのAttributes Inspectorペインを選択する
- BehaviorのVisible At Launchチェックボックスをオフにする
としておく。
メニューの追加とアクション接続
UtilitiesエリアのObject libraryペインからMenuを選択し、トップレヴェルに追加する。
追加したメニューは三つのメニュー項目(Item1、Item2、Item3)を持っているので、そのうちのふたつを削除する。残ったひとつのタイトルをQuit StatusBarAppに変更する。Quit StatusBarAppメニュー項目からInterface Builder DockのApplicationまでControlキィを押しながらドラッグを行い、terminate:
アクションと接続する。これでQuit StatusBarAppメニュー項目を選択したときにアプリケーションを終了できるようになる。
メニューのアウトレット接続
追加したメニューをコードから参照できるようアウトレット接続を行う。
前準備としてAppDelegate
にクラスエクステンションを追加する。AppDelegate.m
ファイルを開き、@implementation AppDelegate
行の前に以下の記述を追加する。
@interface AppDelegate ()
@end
次に、XIBファイルを開いた状態でAssistant editorを表示し、そこにAppDelegate.m
を開く。Interface Builder Dockから追加したメニューオブジェクトを選択し(XIBにはじめからあるMain Menuと間違えないように注意)、Controlキィを押しながらAppDelegate.m
のクラスエクステンション内までドラッグする。そうするとダイアログがポップアップ表示されるので、
- NameでstatusMenuを入力する
- Storage でWeakを選択する
とし、Connectボタンをクリックする。
アウトレット接続をヘッダファイルで行う場合は実装ファイルにクラスエクステンションを追加する必要はないが、ここではメニューを参照する変数は外部に公開する必要はないので、実装ファイルへ退避させた。
ステータス項目を表示する
AppDelegate.m
を開く。まずステータス項目を保持するインスタンス変数を宣言する。
@implementation AppDelegate
{
NSStatusItem *_statusItem;
}
次に以下のようなメソッドsetupStatusItem
を定義する。
- (void)setupStatusItem
{
NSStatusBar *systemStatusBar = [NSStatusBar systemStatusBar];
_statusItem = [systemStatusBar statusItemWithLength:NSVariableStatusItemLength];
[_statusItem setHighlightMode:YES];
[_statusItem setTitle:@"StatusBarApp"];
[_statusItem setImage:[NSImage imageNamed:@"StatusBarIconTemplate"]];
[_statusItem setMenu:self.statusMenu];
}
ここで行っていることを行ごとに説明すると、
-
NSStatusBar
のsystemStatusBar
クラスメソッドでステータスバーを取得する。
-
NSStatusBar
のstatusItemWithLength:
インスタンスメソッドでステータス項目を新しく生成し、_statusItem
インスタンス変数に保持する。このメソッドの引数にCGFloat
の数値を渡すと固定幅のステータス項目が生成されるが、たいていは項目の内容によって幅が可変になるNSVariableStatusItemLength
定数を用いればよい。
なお、このメソッドの返り値はretain
されていないオブジェクトであり、オブジェクトが解放されると同時にステータスバーから項目も消えてしまう。したがって、ARC環境では強参照なインスタンス変数やプロパティで、非ARC環境ではretain
してからインスタンス変数に入れるかretain
を指定したプロパティで保持しておく必要がある。
-
setHighlightMode:
にYES
を渡すことで、ステータス項目が選択されたときに、選択された状態が視覚化される(背景が青くなる)。通常はYES
を渡しておく。
-
setTitle:
でステータス項目にタイトル文字列を指定する。タイトルが必要なければ不要だが、後述のアイコン等の内容物がないとステータス項目が見えない。
-
setImage:
でステータス項目にアイコン画像を指定する。画像ファイルはあらかじめプロジェクトに追加しておく。アイコンが必要なければ不要だが、前述のタイトル等の内容物がないとステータス項目が見えない。
なお、上記のコードで画像のファイル名がTemplate
で終わった形式になっているのは、その形式の画像ファイルからNSImage
を生成すると自動的にテンプレート画像として扱ってくれるからである。テンプレートとして指定された画像をボタンやセグメンテッドコントールに指定すると、それらを選択したとき等にシステムが自動でイイ感じに画像を扱ってくれる(色の反転や非アクティヴ時のグレーアウト等。ただしテンプレート画像は黒と透明のみ)。今回のステータス項目のアイコン画像もテンプレート画像を指定しておくことで選択時にアイコンの色を反転してくれる。
ファイル名がTemplate
で終わっていない画像でも、NSImage
のsetTemplate:
メソッドを使うことでテンプレート画像として扱うことができる。
-
setMenu:
で前項でアウトレット接続したメニューを指定する。これでステータス項目を選択したときにメニューが表示されるようになる。
最後にapplicationDidFinishLaunching:
内でいま実装したsetupStatusItem
メソッドを呼び出す。
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self setupStatusItem];
}
applicationDidFinishLaunching:
はアプリケーションの起動が終了した時点で呼ばれるので、そのタイミングでステータスバーにステータス項目が表示されるようになる。
ビルドして実行
プロジェクトをビルド、アプリケーションを実行すると、ステータスバーにアイコンとタイトルテキストが配置され、それを選択するとメニューが表示されることを確認する。ウィンドウやDockアイコンは表示されないこと、ステータス項目が選択されたときにタイトルやアイコンの色が反転されること、Quit StatusBarAppメニュー項目を選択するとアプリケーションが終了することも確認する。
以上により、ステータスバーアプリケーションの基本的な実装が完了した。実際に何かの仕事をさせる場合は、タイマ等を使って定期的に処理をしたり、何らかのイヴェントをフックしたりする必要がある。
2012年10月25日, 編集履歴
OS Xアプリケーションの振る舞い等を設定するときに用いる環境設定ウィンドウを作る。
環境設定ウィンドウについて
環境設定ウィンドウの特徴は、
- Escapeキィ(もしくはCommand-.)で閉じられる
- アクティヴ時にはメインウィンドウになっている
- ツールバーを持ち、そのツールバー項目をクリックすることでヴューを切り替える
- ヴューを切り替える際にウィンドウのサイズがアニメーションを伴って変化する
ここでは、上記のような特徴を持つ環境設定ウィンドウの、ウィンドウとしての振る舞いを実装する。実際にアプリケーションの設定をする方法は別論(NSUserDefaults
等を用いる)。
今回作成したプロジェクトはGenjiApp/PrefWindowApp - GitHubで公開するので参照のこと。
なお、Xcodeはヴァージョン4.5.1を用いた。
Xcodeプロジェクトの作成
Xcodeを起動し、New Project…からOS X ApplicationのCocoa Applicationを選択する。ここではProject NameをPrefWindowAppとし、Create Document-Based Applicationはオフに、Use Automatic Reference Countingをオンにして、適当な場所にプロジェクトを作成した。
ウィンドウコントローラの追加
プロジェクトに環境設定ウィンドウを管理するクラスを実装する新しいファイルを追加する。New File…からOS XのCocoaカテゴリィのObjective-C Classを選択肢、ClassをPreferencesWindowController
、Subclass ofをNSWindowController
にして、With XIB for user interfaceチェックボックスをオンにしてファイルを作成する(PreferencesWindowController.h/m/xib
のみっつのファイルが作成される)。
今回の環境設定ウィンドウはふたつのヴューを切り替えて操作するものとする。それぞれのヴューを判別するための定数を列挙型で適当な場所に宣言しておく。ここではPreferencesWindowController.m
ファイルに宣言した。
enum PreferencesViewType {
kPreferencesViewTypeGeneral = 100,
kPreferencesViewTypeAdvanced,
};
typedef NSInteger PreferencesViewType;
ふたつのヴューが判別できれば値は何でも構わない。
NSWindow のサブクラスを作成する
環境設定ウィンドウの振る舞い
一般的な環境設定ウィンドウはEscape キィ(あるいはCommand-.)で閉じることができる。環境設定ウィンドウを単なるNSWindow
で実装するとこの振る舞いを実現できない。その一方でNSPanel
というクラスではEscapeキィでウィンドウを閉じることができる。しかし、単なるNSPanel
ではメインウィンドウにはなれない。環境設定ウィンドウはアクティヴ時にはメインウィンドウになっていなければならない。さらにメニューバーのWindowメニューにはアプリケーションで開かれているウィンドウの一覧が表示され、環境設定ウィンドウも表示されるが、NSPanel
はWindowメニューに表示されない。
メインウィンドウとキィウィンドウ
OS Xアプリケーションのウィンドウには、メインウィンドウとキィウィンドウ、および非アクティヴなウィンドウというみっつの状態が存在する。
非アクティヴなウィンドウとはフォーカスが当たっていないウィンドウのことで、他のウィンドウの下にあったり、他のアプリケーションがアクティヴな状態のとき、つまりユーザの現在の操作対象にはなっていないウィンドウのことである。非アクティヴなウィンドウ自身や、そのUI部品は色がグレーアウトし、ウィンドウのドロップシャドウが小さくなる。
キィウィンドウとはフォーカスが当たっており、ユーザの現在の操作対象になっているウィンドウのことである。
メインウィンドウとはユーザの現在の操作対象になっているウィンドウのことである。メインウィンドウがキィウィンドウとなっていることが多いが、他のウィンドウ(パネル)がキィウィンドウになっていることもある。メインウィンドウとは別にキィウィンドウが存在する場合、メインウィンドウのタイトルバーの閉じるボタン等はグレーアウトするが、ウィンドウ自身やそのUI部品はグレーアウトせず、ウィンドウのドロップシャドウも大きいままである。
マウスやキィボードの入力は始めにキィウィンドウに対して送られ、キィウィンドウがそれに応えられない場合はメインウィンドウに伝搬される。いま、メインウィンドウとキィウィンドウが別々にあったとする。キィウィンドウにはテキストフィールドが置いてあり、そこにフォーカスが当たっているとする。テキストエディタアプリケーションだったとして、メインウィンドウがエディタ本体のウィンドウで、キィウィンドウとして検索パネルのような物が表示されている状態である。ユーザの通常のキィ入力はキィウィンドウ(検索パネル)のテキストフィールドで処理される(テキストフィールドに文字が入力される)。Command-Vのようなキィボードショートカットもテキストフィールドで処理される(ペースト)。これらはキィウィンドウが応答できる処理だからである。キィウィンドウが応答できない処理の場合、たとえばCommand-Sの保存はメインウィンドウに伝搬され、エディタアプリケーションの保存ダイアログが開く。
環境設定ウィンドウを開くと、それまでのメインウィンドウは非アクティヴになり、環境設定ウィンドウがメインウィンドウとなる。これは、それまでのメインウィンドウやそのUI部品がグレーアウトし、そこで処理可能であったメニューコマンドが使用不能に変わることから見て取れる。
NSWindow
のサブクラス化
つまり、環境設定ウィンドウを実装するには、次のどちらかの手法をとる必要がある。
NSWindow
で実装してEscapeキィの動作を横取りしウィンドウを閉じられるようにする
NSPanel
で実装してメインウィンドウになれるように、ウィンドウメニューに表示されるようにする
ここではかんたんな前者の手法をとることにする。
NSWindow
のサブクラスを作成するにあたり、新たにファイルを作成してもよいが、必要なコードは微少なのでPreferencesController
に同居させる。
PreferencesWindowController.h
を開き、NSWindow
のサブクラスを作成する。名前はPreferencesWindow
とした。
@interface PreferencesWindow : NSWindow
@end
次にPreferencesWindowController.m
を開き、PreferencesWindow
を実装する。NSWindow
をEscapeキィで閉じられるようにするには、cancelOperation:
を実装してその中でウィンドウを閉じるようにすればよい。また、環境設定ウィンドウのツールバーを隠せてしまってはまずいので、ツールバーの表示、非表示をトグルするメニュー項目を無効化しておく。
@implementation PreferencesWindow
- (void)cancelOperation:(id)sender
{
[self close];
}
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
SEL action = [anItem action];
if(action == @selector(toggleToolbarShown:)) return NO;
return [super validateUserInterfaceItem:anItem];
}
@end
作成したPreferencesWindow
は次項で用いる。
環境設定ウィンドウの XIB ファイルを編集する
ウィンドウの設定を行う
PreferencesWindowController.xib
ファイルを開く。XIBファイルにはあらかじめウィンドウオブジェクトが用意されているので、そのウィンドウオブジェクトを選択し、UtilitiesエリアのIdentity InspectorペインのCustom Classで前項で作成した PreferencesWindow
クラスを指定する。これでウィンドウはEscapeキィで閉じることができるようになる。
ウィンドウオブジェクトを選択したままでAttributes Inspectorペインにて各種設定を行う。
- ControlsでResize、Minimizeチェックボックスをオフにする
- AppearanceでShows Toolbar Buttonチェックボックスをオフにする
- BehaviorでVisible At Launchチェックボックスをオフにする
次にウィンドウにツールバーを追加する。UtilitiesエリアのObject libraryペインからツールバーを選択し、ウィンドウにドラッグ・アンド・ドロップする。ウィンドウ上部にツールバーが追加されるので、そのツールバーをクリックして選択し、UtilitiesエリアのAttributes InspectorペインのCustomizableチェックボックスをオフにする。
追加したツールバーを選択した状態でもう一度ツールバーをクリックすると、ツールバーに表示する項目を編集できるようになる。ディフォルトでいくつかの項目が用意されているが、これらをすべて削除し、Object libraryペインからImage Toolbar Itemをふたつドラッグ・アンド・ドロップして追加する。追加したImage Toolbar ItemのAttributes Inspectorペインで、
- Image Nameでそれぞれ
NSPreferencesGeneral
とNSAdvanced
を指定する
- Label、Palette Labelでそれぞれ
General
とAdvanced
を入力する
- Tagでそれぞれ
100
、101
を入力する
- Behavior でSelectableチェックボックスをオンにする
追加した Image Toolbar ItemがAllowed Toolbar Itemsにあるので、設定が終わったら、それをDefault Toolbar Items欄にドラッグ・アンド・ドロップしておく。
Image Name、Label、Palette Labelの値は任意であるがここでは上記のようにした。Tagの値は前項で宣言した列挙型定数に対応している。
ヴューを追加し、アウトレット接続を行う
UtilitiesエリアのObject LibraryペインからCustom Viewをトップレヴェルにふたつ追加する(ウィンドウ内への追加ではない)。このヴューの上に環境設定ウィンドウのUI部品が乗ることになる。とりあえず、Labelでも追加して適当な文字列を入力しておく。ヴューのサイズは適当に違う大きさにしておくと、あとで実装するヴュー切り替えによるウィンドウのリサイズが解りやすくなる。
XIBファイルを開いた状態でAssistant editorを表示し、そこにPreferencesWindowController.m
を開く。それぞれのヴューからControlキィを押しながらドラッグを行い、Assistant editorで開いたPreferencesWindowController.m
のクラスエクステンション部分までドラッグする。そうするとダイアログがポップアップ表示されるので、
- Nameでそれぞれ
generalView
、advancedView
を入力
- StorageでWeakを選択
とし、Connectボタンをクリックする。これでコードからそれぞれのヴューを参照できるようになる。
アクションを接続する
続いて、ウィンドウのツールバーに追加したImage Toolbar ItemのひとつからControlキィを押しながらドラッグを行い、Assistant editorに開いたPreferencesWindowController.m
のクラスエクステンション部分までドラッグする。そうするとダイアログがポップアップ表示されるので、
- ConnectionでActionを選択する
- Name で
switchView
を入力する
とし、Connectボタンをクリックする。これでImage Toolbar Itemをクリックしたときに呼ばれるアクションメソッドとの接続ができた。残ったもうひとつのImage Toolbar ItemからもControl-ドラッグを行い、いま作成したswitchView:
アクションメソッドの宣言文でドロップして接続を行い、同じメソッド呼び出しができるようにしておく。
PreferencesWindowController
の実装
ヴュー切り替えアクションの実装
PreferencesWindowController.m
を開き、以下のようなswitchView:
メソッドを実装する。前項でアクション接続した際にスケルトンが作成されているので、中身を埋めていく。
- (IBAction)switchView:(id)sender
{
NSToolbarItem *item = (NSToolbarItem *)sender;
PreferencesViewType viewType = [item tag];
NSView *newView = nil;
switch(viewType) {
case kPreferencesViewTypeGeneral: newView = self.generalView; break;
case kPreferencesViewTypeAdvanced: newView = self.advancedView; break;
default: return;
}
NSWindow *window = [self window];
NSView *contentView = [window contentView];
NSArray *subviews = [contentView subviews];
for(NSView *subview in subviews) [subview removeFromSuperview];
[window setTitle:[item label]];
NSRect windowFrame = [window frame];
NSRect newWindowFrame = [window frameRectForContentRect:[newView frame]];
newWindowFrame.origin.x = windowFrame.origin.x;
newWindowFrame.origin.y = windowFrame.origin.y + windowFrame.size.height - newWindowFrame.size.height;
[window setFrame:newWindowFrame display:YES animate:YES];
[contentView addSubview:newView];
}
ここで行っていることは、
sender
のtag
から切り替え先となる新しいヴューを判別する
- ウィンドウの
contentView
がサブヴューを持っている場合はそれを取り除く
sender
のlabel
を用いてウィンドウのタイトルを設定する
- ウィンドウと切り替え先ヴューの
frame
から、切り替え後のウィンドウのframe
を計算する
- 計算した新しいウィンドウの
frame
をアニメーション付きで適用する
- ウィンドウの
contentView
に切り替え後のヴューを追加する
このswitchView:
メソッドは環境設定ウィンドウのツールバー項目をクリックしたときに呼ばれる。つまり、引数のsender
はそのクリックしたImage Toolbar Item(NSToolbarItem
)への参照である。Image Toolbar Itemには前項でTag欄に値を設定している。この値はふたつあるヴューのどちらかに対応しているので、その値を取り出してどちらのImage Toolbar Itemがクリックされたか、言い換えればどちらのヴューへの切り替えなのかを判別する。それぞれのヴューはアウトレット接続されたプロパティとしてコードから参照できる。
ウィンドウの新しいframe
について、OS Xの座標系は左下原点なので、単にcontentView
のサイズを変更しただけではウィンドウの上端が動いてしまうことになる。したがって、ヴュー切り替え前のウィンドウの上端座標から切り替え後のウィンドウの高さを引くことで、新しいウィンドウの原点座標を得ることになる。
初期選択状態の設定
次にwindowDidLoad
メソッドを以下のように書き換える。
- (void)windowDidLoad
{
[super windowDidLoad];
NSWindow *window = [self window];
NSToolbar *toolbar = [window toolbar];
NSArray *toolbarItems = [toolbar items];
NSToolbarItem *leftmostToolbarItem = [toolbarItems objectAtIndex:0];
[toolbar setSelectedItemIdentifier:[leftmostToolbarItem itemIdentifier]];
[self switchView:leftmostToolbarItem];
[window center];
}
windowDidLoad
メソッドで、ツールバー項目の初期選択状態や、最初に表示されるヴューの設定を行う。ここではツールバー項目の一番左のものが最初に選択された状態であるとし、それに対応するヴューが最初に表示されるようにした。適当な方法で選択状態を保存しておき、次回起動時にその状態を復元してもいいかもしれない。
半シングルトン化
環境設定ウィンドウは通常そのアプリケーション全体を通してひとつしか存在しない。たとえばテキストエディタアプリケーションは複数のエディタウィンドウを開くことができるだろうが、環境設定ウィンドウをどこからいつ呼び出しても同じものが使い回されて表示される。
これをかんたんに行うには、MainMenu.xib
のトップレヴェルにPreferencesWindowController
を追加し、アウトレット接続しておけばよい。そうするとアプリケーション起動と共にPreferencesWindowController
がインスタンス化され、アウトレット接続された変数を用いる限りアプリケーション起動中はずっと同じインスタンスが使われる。
しかし環境設定ウィンドウは使うときは使うが、使わないときはまったく使わない。使わないのにアプリケーション起動中ずっとメモリィを消費してしまうのは無駄なので、ここでは必要なときにコードからインスタンスを生成する手法をとる。また、同じインスタンスを使い回すために、シングルトンデザインパターンを用いる。
シングルトンとは、かんたんに言えば、クラスが自身のインスタンスをただひとつのみ生成し、それを使い回す手法である。通常はインスタンスを生成、取得するクラスメソッドを用意する(Cocoa の場合このクラスメソッドにはshared
あるいはdefault
等の接頭辞を付けるのが習わしであるようだ)。
PreferencesWindowController.h
を開き、インターフェイス部を以下のように編集する。インスタンスを生成、取得するクラスメソッドの名前はsharedPreferencesWindowController
とした。
@interface PreferencesWindowController : NSWindowController
+ (PreferencesWindowController *)sharedPreferencesWindowController;
@end
次にPreferencesWindowController.h
を開き、メソッドを実装する。
+ (PreferencesWindowController *)sharedPreferencesWindowController
{
static PreferencesWindowController *sharedController = nil;
if(sharedController == nil) {
sharedController = [[PreferencesWindowController alloc] init];
}
return sharedController;
}
このクラスメソッドの初回呼び出し時にはstatic変数sharedController
はnil
であり、したがってif
文に入ってインスタンスを生成し、それを返す。次回以降の呼び出しではsharedController
にはインスタンスが入っているのでif
文をスルーしてそのまま返すのみである。
クラスメソッドから呼ばれるinit
メソッドもオーヴァライドする。
- (id)init
{
self = [super initWithWindowNibName:@"PreferencesWindowController"];
if(self) {
// Initialize
}
return self;
}
コードからインスタンスを生成し、同時にウィンドウのXIBファイルの読み込みも済ませるために、init
内でinitWithWindowNibName:
メソッドを用いる。こうしておけば、インスタンス生成側はalloc
、init
とするだけでウィンドウコントローラのインスタンス生成と対応するXIBファイルの読み込みを行うことができるようになる。
これにより、sharedPreferencesWindowController
クラスメソッドを通してインスタンスを得る場合に限り、同じインスタンスが返されることになり、PreferencesWindowController
のシングルトン化ができた。完璧にシングルトンとするにはメモリィ管理系のメソッドをオーヴァライドする必要があるが、自分がシングルトンデザインパターンを用いるクラスを実装して、自分がそのクラスを使用するということを前提において、ここでは省略する。
環境設定ウィンドウを表示するアクションメソッドを実装する
前項でPreferencesWindowController
の実装が終わったので、今度はそれを呼び出す側を実装する。環境設定ウィンドウはアプリケーション起動中はいつでも呼び出せる必要がある。MainMenu.xib
はアプリケーション起動時に読み込まれ、そこに登録されたオブジェクトはアプリケーション起動中ずっとインスタンス化されたままなので、今回の用途に適合する。MainMenu.xib
には初めからAppDelegate
がトップレヴェルに登録されており、ここに環境設定ウィンドウを表示するアクションメソッドを実装するのがかんたんである。
MainMenu.xib
ファイルを開き、Assistant editorにAppDelegate.h
を開く。MainMenu.xib
のMain Menuを選択し、そこにあるアプリケーションメニュー(アプリケーションの名前が付いたメニュー項目)内のPreferences…メニュー項目を選択する。Preferences…メニュー項目からControl-ドラッグしてAssistant editorに開いたAppDelegate.h
の@interface
内にドロップしてアクションを接続する。表示されるポップアップではConnectionでActionを選択、NameでshowPreferencesWindow
とする。
AppDelegate.m
を開いて、PreferencesWindowController
クラスを使えるようにするため、ファイル先頭に#import
ディレクティヴを追加する。
#import "PreferencesWindowController.h"
showPreferencesWindow:
メソッドはアクション接続時にスケルトンが作成されているので、以下のように編集する。
- (IBAction)showPreferencesWindow:(id)sender
{
PreferencesWindowController *sharedController = [PreferencesWindowController sharedPreferencesWindowController];
[sharedController showWindow:sender];
}
前項で作成したPreferencesWindowController
のsharedPreferencesWindowController
クラスメソッドを用いて、シングルトンインスタンスを取得し、showWindow:
インスタンスメソッドで環境設定ウィンドウを表示する。
ビルドして実行
以上により、環境設定ウィンドウを持つアプリケーションが完成した。プロジェクトをビルド、アプリケーションを実行し、アプリケーションメニューからPreferences…を選択して、以下のことを確認する。
- 環境設定ウィンドウが表示されるか
- 環境設定ウィンドウがメインウィンドウになり、それまでのメインウィンドウが非アクティヴになるか
- 環境設定ウィンドウがEscape(もしくはCommand-.)で閉じられるか
- 環境設定ウィンドウがアクティヴのときにViewメニューのShow/Hide Toolbarメニュー項目が無効化されているか
- 環境設定ウィンドウのツールバー項目のクリックでヴューが切り替わるか
- ヴューの切り替わりと同時にウィンドウサイズがアニメーションを伴って変更されるか