Xcode で GUI アプリ 3

すみません、以前の記事は見当違いなことを書いていたかも。

メニューアイテムから各オブジェクトの操作をするには、この記事読んでください。

 

1 まず、アイテムと AppDelegate が結びついていることが前提。

2 ここがポイントなんでしょうが、以下のようにアイテムをファーストレスポンダーに結びつけ、その中から AppDelegate で定義したメソッドを選ぶ。

3 これは言われなければ絶対に気がつかない。アクションのコードを AppDelegate から動作せたいコントローラークラスにコピペ。

いや、これでうまくいくんだな。

 

アーカイブ


Objective-C の GUI アプリシリーズの3回目。

前回まででプロジェクトを作成、window を表示させ、その window に若干の制御(表示位置をセンターに持ってくるだけだが)を加えた。

よりデスクトップアプリっぽさを出すために、メニューを操作してみたい。

一番簡単にはこの記事で示されているように

クラスの追加→メニュアイテムと関連付ける

だが、Objective-C でうまくいくか?

上の方法が汎用性があるかどうかはわからないが、以下のような手続きで Objective-C でもうまくいった

すみません、元の操作は、単に App Delegate の名前を変えていただけかも。

結局、以下の方針で NSWindowController のサブクラスを作りました。

ちょっと自信ないですが、一旦、メニューバーを引き受ける object を追加して(↓下図参照。これをしないとイベントを引き受けるクラスを新規に追加できない)そのカスタムクラスを実装する(ワイの場合は NSWindowController のサブクラスとした)、という方針が素直でいいように思います。

一応、これでうまくいきました。悪名高き Could not insert new outlet connection エラーが頻発しましたが。。。

これも NSMenuDelegate 削ったら、エラーがピタリと止んだりと、ここら辺は、なんか感覚です。

cocoa のしきたり、奥が深い。

 

メニューを管理するクラスの作成

まず、メニューを管理するクラス(Menu1)を作成し、メニューバーの UI の所属先をこのクラスに変更。

Ctrl 押しながら・・・

こうした後、メニューアイテムをあの「ctrl 押しながら・・・」ってやつでソースコードに結びつけた。

コードの実装

IBAction の実装は以下のようにシンプルにした。

#import "Menu1.h"

@implementation Menu1

- (IBAction)test:(NSMenuItem *)sender{
    NSLog(@"test");
}
@end

動作確認

これでエラーを吐くことなく正常に動作した。

メニューアイテムが明示的に実体化されているので、大抵の場合は、この方法でいいのかもしれない。

Xcode で GUI アプリ 2

前回まででまずはプロジェクトはできた。

しかし、ViewerController に関しては、Xcode 任せの状態であるため、次に、プロジェクトにこの派生クラスの追加をしよう。

NSWindowController 派生クラスの追加

まず、プロジェクト(本当は「ターゲット」だが)に NSWindowController 派生クラスを追加する。

名前はなんでもいいのだが、諸々の事情で PHWindowController とした。

ファイルが追加できたら、画面左にある(↓)の項目を切り替えて、現在の WindowController を PHWindowController に変更する。

この時点で run させても、変更前と動作は変わらない。

独自機能を追加するため windowDidLoad をオーバーライドする。
なお、Objective-C における override はかなり簡単で、親クラスの同名メソッドを書き換えるだけでいい。

今回は

    [self.window makeKeyAndOrderFront:self];
    [self.window center];

の2行を追加した。

ヘッダファイル PHWindowController.h は以下の通り。

#import <Cocoa/Cocoa.h>

NS_ASSUME_NONNULL_BEGIN

@interface PHWindowController : NSWindowController
//- (instancetype)init;
//@property(strong) NSWindow *window;

@end

NS_ASSUME_NONNULL_END 

PHWindowController.m は以下の通り。

#import "PHWindowController.h"

@interface PHWindowController ()

@end

@implementation PHWindowController

/*
- (instancetype)init
{
  self = [super init];
  return self;
}
 */

- (void)windowDidLoad {
    [super windowDidLoad];
    
    [self.window makeKeyAndOrderFront:self];
    [self.window center];

}

@end

これでランさせると以下のようになる。

変更前には画面やや左下に位置していたウィンドウが中央に配置されるようになった。

ところで、init メソッドは必ずしもいらないようだ。
また、NSWindowController を宣言した時点で window は持っているようで、自分自身のオブジェクトのウィンドウにアクセスする場合は self.window でいいようだ。
こういうクセみたいなものは、やってみないとわからない。

(続く)

Xcode で GUI アプリ

これまで、主にコマンドラインツールを作成してきた。

ある程度、Objective-C の様子もわかったきたと思うので、GUI アプリの作成に手をつけてみよう。

プロジェクトの作成〜実行

Xcode を起動して新しいプロジェクトをつくる。

Command Line Tool ではなく、App の方を選ぶ。

次に Product Name などを決めるダイアログが出現する。

ワイは cocoa-sample としたが、名前などは適当に。

言語は Objective-C で。

悩みどころは、interface の選択で、いくつか候補はある。

アップルのアプリの GUI に関しては、歴史的には、 Nib → Xib → Storyboard → SwiftUI の順番で発展してきた。
(ただし、現状では開発言語を Objective-C を選択した場合は SwiftUI は選べない。なお、Nib は Next Interface Builder から。Xib の X は XML から)

iOS の影響なのか Storyboard で説明してあるサイト・教科書がほとんどだが、今でもレガシーなプロジェクトでは Xib は使われている。さすがに Nib は見かけないが。

GUI のシステムは度々変更されるので、表面的な操作だけではなく、その意味を把握することを意識しておくと少々の変更があっても対応することができると思う。

プロジェクトを作成するといくつかのファイルが自動生成される。

いきなりだが、この時点で run させてみよう。

ここまでの手順が間違ってなければ、以下のようなウィンドウが出現する。

ここまで1行のコードも書かずに Mac ネイティブなウィンドウをディスプレイ上に表示できているわけだから、世間的にはこちらの流儀がメインとなっていくのはわからないでもない。

しかし、である。

知識や理解などがこのレベルに留まっている人は多いようだ。

このレベルだと狙った機能を独自に実装する、というような作業は無理ではなかろうか。

自動生成されたファイルなどの意味をもう少々深掘りしよう。

main.m の意味

main.m の具体的なコードは以下のようになっている。

#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
    }
    return NSApplicationMain(argc, argv);
}

GUI メインで学んできた初学者に「argv って何?」と聞いてもうまく答えられる人はそう多くない。

ワイがまず最初にコマンドラインツールやらポインタの説明をしたのは、ここら辺の理解を深めるためってのもある。

要するにこのコードは argc と文字列配列(のポインタ)argv を NSApplicationMain() に与え、そこからの返り値を待っているという機能をになっている。

では、NSApplicationMain が何かといえば、アプリケーションを管理しているクラスを呼び出す関数ということになる。

ここら辺は日本語記事もあるし、アップル公式の説明でもなんとかなると思う。

ところで、初学者にどの程度まで説明するのか?というのは難しい問題も孕んでいる。

NSApplicationMain にしても、「NS は NextStep に由来する」のようなマメな知識をさしはさんでいるサイトはあるが、「Objective-C/C++ では名前空間の概念がないため、クラスやそのメンバー関数を命名する際には慣例的に大文字2文字を先頭につける」といったところまで説明しているサイトはほぼない

AppDelegate と ViewController

だんだん理解があやしくなってくるのが、AppDelegate や ViewController だろう。

ただ、ちょっと先を急ぎたいので、ここはワイも流させてもらう。

あるアプリが main.m を経て NSApplicationMain で示される何かで管理されているとき、全てが管理されていたのでは、独自実装はできなくなってしまうので、動作の要所要所で独自処理を指示できる箇所が AppDelegate だ、という説明ではどうだろう?

WindowController

cocoa アプリのプロジェクトを作成したとき、AppDelegate と ViewController に対応するファイルは自動で生成される。

が、実際にウィンドウシステムを動作させるとき、これを管理するクラスも必要だ。
結論から先に書くと、これが WindowController クラスで、Xcode 上でも確認できる。

Main.storyboard を選んだ際、これを構成する Scene が表示されるが、Window Controller はしっかりありますよね?

ファイルでは生成されないのだが、裏では動いているという理解でいいと思う。

そのほか

そのままでは機能はしないが、メニュー構造もできている。

つまり、プロジェクトを作成しただけでも、デスクトップアプリに必要な機能はほぼ提供されているのだ。

 

(続く)

Objective-C 再入門 -1.0.3-

今回は @autoreleasepool の話。

と言っても、私はこれに関して大して詳しくない。

オートリリースプールの使い方と基本』という素晴らしくよくまとまった記事があるので、そちらを参照してください。

オートリリースプールは通常、裏で自動的に生成・破棄されているので、基本的には意識しないで autorelease が利用できるようになっています。

ただし、無造作に独立して動いている訳ではないので、処理を長くキープし続けるような場面では、意図的にオートリリースプールを解放してあげないと、解放待ちのインスタンスでメモリが溢れてしまう危険性があります。

という解説の後に、以下のようなサンプルコードが提示されている。


// 長いループの中でオートリリースプールを生成することで、解放待ちのインスタンスが溜まってしまうのを防ぎます。
while (!isCancelled)
{
 @autoreleasepool
 {
   // この中で生成された autorelease なインスタンスは…
   self.label.text = [NSString stringWithFormat:@"Step: %d", self.step];
 }
 // 次のループ処理に入る前に release されます。
}

Xcode で Objective-C のプロジェクトを生成されると、もれなく

と @autoreleasepool{} がついてくるが、なるほど、こういう理屈だったわけですか。