アプリ内でボタンは頻繁に使用する要素の一つです。今回はCocoa版Hello Worldに終了ボタンを追加して、ボタンからも終了できるようにします。
この記事では前回の記事で作成したCocoa版Hello Worldの続きから行います。前回の記事に沿って作成している場合はそれを使用してください。作成されていない場合は、前回の記事の末尾からサンプルコードをダウンロードしてご利用ください。
アクションとターゲット
AppKitでボタンを作るときには「アクション」と「ターゲット」を使用します。AppKitではボタン自体はビューの一つです。ボタンがクリックされると、ボタンに設定されたアクションが実行されます。アクションを実行するのはターゲットです。
ターゲットの実体について
アクションを実行するターゲットの実体は、NSResponder
クラスを継承したサブクラスのインスタンスです。アプリ独自の処理は、アプリ側でクラスを実装します。AppKitにもNSResponder
クラスの継承クラスは沢山あります。例えば、ウインドウを実装したNSWindow
クラスやNSWindowController
クラスもNSResponder
クラスを継承しているので、ターゲットになります。
アクションの実体について
アクションの実体はセレクタです。セレクタはObjective-Cのメソッドの内部表現です。アプリ開発をする上では、セレクタを設定するということは、メソッドを設定することだと考えて頂ければ大丈夫です。
例えば、Objective-Cの次のようなメソッドがあるとします。
@implementation TestObj
- (void)doSomething:(id)sender
{
}
@end
このdoSomething
メソッドに対するセレクタは次のように書きます。
SEL theSelector = @selector(doSomething:);
Objective-Cと書きましたが、Swiftではどうなるのでしょうか?Swiftでは、このObjective-Cのクラスのメソッドに対するセレクタは次のように書きます。
let theSelector = #selector(TestObj.doSomething(_:))
Swiftで書かれたクラスのメソッドに対しても同様にセレクタを取得できます。但し、Swift側のコードで次のようにします。
NSObject
クラスを継承する@objc
アノテーションを付けてメソッド定義する
例えば、次のようなコードです。
class TestSwiftObj: NSObject {
@objc func doSomething(_ sender: AnyObject?) {
}
}
終了ボタンを追加する
終了ボタンを作っていきます。AppKitではボタンにターゲットとアクションを設定します。ターゲットですが、Cocoa版HelloWorldはとても小さなアプリなので、ViewController
をターゲットにします。
アクションの実装
ViewController
をターゲットにするので、アクションとなるメソッドもViewController
に追加します。次のようにViewController.swift
にコードを追加します。
import Cocoa
class ViewController: NSViewController {
// 省略
@IBAction func terminateApp(_ sender: Any?) {
NSApplication.shared.terminate(sender)
}
}
アクションとして定義する
アクションにするメソッドは次のような形式のコードで実装します。
@IBAction func methodName(_ sender: Any?) {
}
@objc
ではなく@IBAction
を使用します。@IBAction
はXcodeからアクションとしてメソッドを認識させるために必要なアトリビュートです。@IBAction
も、メソッドをObjective-Cのセレクタと互換性のある形でエクスポートするので、@objc
が不要になります。(@objc
の処理を内包します)
終了ボタンをビューに追加する
次のように操作して、終了ボタンをビューに追加します。
(1) Main.storyboard
を開きます。
(2) View Controller Scene
を選択します。
(3) ツールバーの「+」ボタン(「Library」ボタン)をクリックします。
(4) ライブラリウインドウから「Push Button」をビューの「Hello World」の少し下にドラッグ&ドロップします。ちょうど良いくらいの場所でガイドが表示されます。
(5) 配置したボタンをダブルクリックします。タイトルを編集可能になるので、「Quit」と入力して「Return」キーを押します。
オートレイアウトの設定
次にオートレイアウトを設定します。「Hello World」というラベルは中央に表示されるようになっています。「Quit」ボタンはその下に表示されるように設定します。次の様に操作します。
(1) 「Quit」ボタンが選択されていなかったら選択します。
(2) 「Add New Constraints」ボタンをクリックします。
(3) 「Width」と「Height」をチェックし、「Add 2 Constraints」をクリックします。これにより、幅と高さが固定されます。
(4) もう一度「Add New Constraints」をクリックし、上側の余白欄のポップアップアイコン(下向きの三角)をクリックし、「Hello World (current distance = 8)」を選択します。これにより、「Hello World」というラベルの下に8px
の位置にY座標が設定されます。
(5) 「Align」をクリックします。
(6) 「Horizontally in Container」をチェックし、「Add 1 Constraint」をクリックします。
ボタンにアクションを設定する
最後に「Quit」ボタンのアクションにterminateApp
メソッドを設定します。次のように操作します。
(1) 「Quit」ボタンを右クリックドラッグ、または、「Control」キーを押しながらドラッグします。青い線が現れるので、「View Controller Scene」の「View Controller」にドロップします。
(2) terminateApp:
を選択します。これで、ボタンのアクションにterminateApp
メソッドが設定されます。
ガイド線について
ボタンを追加するときに表示された水平方向のガイド線ですが、Human Interface Guidelinesに従って空けるべき余白のところで表示されます。また、垂直方向のガイド線は、ちょうどビューの中央を示すものとして表示されています。
ガイド線に従って配置すると、ある程度、良い感じにレイアウトすることができます。
Human Interface Guidelinesはデベロッパーサイトで公開されています。
Fat View ControllerとFat Window Controller
ここではViewController
にビジネスロジックである終了機能を実装しました。その際、「小規模である」ということを強調しました。AppKitは元々MVCで設計されています。
ビューコントローラやウインドウコントローラはコントローラです。コントローラは入力をモデルに伝えることです。終了するという処理はロジックです。ビジネスロジックはモデル側で行うべきであり、コントローラでは行うべきでありません。
しかし、Cocoa版Hello Worldはとても小規模です。このくらいの小規模なアプリで厳密に分割していくのはかえってオーバースペックと考えられました。そのため、ここではViewController
に直接実装しました。
なお、何でもかんでもコントローラに実装してしまい、大きく膨れ上がってしまったビューコントローラやウインドウコントローラを「Fat View Controller」や「Fat Window Controller」などと呼びます。
コードの見通しの良さや保守性などの観点からそのような状態になるのは避けた方が良いでしょう。現実には何度も、そのようなコードを書いてしまったことがありますが。これらのコードもいつかは改善したいですね。
動作テスト
アプリをビルドして実行してみましょう。表示されたウインドウをリサイズして、「Quit」ボタンの位置がおかしくならないことを確認します。
次に「Quit」ボタンをクリックして、アプリが終了するか確認してみましょう。
サンプルコードのダウンロード
今回の記事で作成したサンプルコードはこちらからダウンロードできます。