AppDelegate さて Xib では?

MacOS のデスクトップアプリを Xcode で作成する際、UI として storyboard を選べば、自動で appdelegate は生成されるので、簡単なアプリならば、ここで初期化処理すればいい。

が、レガシーなプロジェクトでは xib が使われている場合も多い。

ちょっと調べますかね。

Xcode を起動し、UI を xib でプロジェクトを作成。

MainMenu.xib が自動で生成されるが、この Objects を管理しているのが AppDelegate という構成になっているようだ。

AppDelegate も自動生成されるが、このクラスは以下のように NSApplicationDelegate を継承している。

なるほど。

ただ、レガシーなプロジェクトではこの継承が明示されていないこともあるようだ。
AppController のような名前になっていて初見でなんのことかわからなかった。
もちろん、
– (void)applicationDidFinishLaunching:
– (void)applicationWillTerminate:
は、実装されていたが。
さらに言っておくと WindowController から AppController のイニシャライズをする、というなんとも風変わりな構成になっていた。

こういうのもあるんですね。

 

 

 

 

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 でいいようだ。
こういうクセみたいなものは、やってみないとわからない。

(続く)

arm Mac で C/C++ ライブラリを x86_64 向けにビルドする

すっかり arm 環境に移行した感のある Mac 界隈だが、なんらかの事情で Intel Mac 向けにアプリなどをビルドしたいという場合がある(案件だとね)。

アプリ自体は Xcode 上での操作でなんとかなると思うのだが、厄介なのはライブラリの類。

使いたい C/C++ のライブラリを M1/M2 Mac 上で x86_64 向けにビルドする必要が出てくる。
いわゆるクロスコンパイルというやつだ。

iOS の場合、simulator が x86_64 でしか動かないので、この手の情報はけっこうあったのだが、 MacOS 向けの情報は皆無だった。

試行錯誤したところ configure 時に以下のようなにコンパイルオプションを与えると、クロスコンパイルできるようだ。

./configure --prefix=/dir/hoge x86_64-apple-darwin64 CFLAGS='-arch x86_64 -O2 -g'

あとは make, make install でいけるっぽい。

–build オプションを使えとか、それっぽい情報はあるにはあった。
が、実行しても惜しいところまではいくんだが、なぜか最終的にはうまくいかなかった。

 

cmake で framework を生成する

この前の記事で、cmake のプロジェクトで cocoa の framework を作成するオプションを見つけたので試してみる。

framework の生成

必ずしも info.plist は必須ではないようだ。
サンプルのうち、以下のように info.plist と CODE_SIGN の部分を潰しても framework は生成される。

CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(TestFramework C)
add_libraryFr(TestFamework SHARED
 TestFramework.h
 TestFramework.c)

set_target_properties(TestFramework PROPERTIES
  FRAMEWORK TRUE
  FRAMEWORK_VERSION C
  MACOSX_FRAMEWORK_IDENTIFIER com.akiba.TestFramework
  #MACOSX_FRAMEWORK_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist
  # "current version" in semantic format in Mach-O binary file
  #VERSION 16.4.0
  # "compatibility version" in semantic format in Mach-O binary file
  #SOVERSION 1.0.0
  PUBLIC_HEADER TestFramework.h
  #XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Developer ID Application: Taro Akiba(123xxx789)"
)

この状態で

cmake -S . -B build

としても Makefile は生成される。もちろん make で TestFramework.framework も生成される。生成するだけならば

FRAMEWORK TRUE

とするだけでいいようだ。

生成された framework を使ってみる

とりあえずお試しなので TestFramework.h と TestFramework.c は以下のような簡単なものにした。

TestFramework.h
#ifndef TESTFRAMEWORK_H
#define TESTFRAMEWORK_H

int twice(int);

#endif
TestFramework.c
#include <stdio.h>
#include "TestFramework.h"

int twice(int n) {
    return 2*n;
}

これで、cmake を走らせると確かに TestFramework.framework はできている。

次に Xcode で別のプロジェクトを作成して TestFramework.framework を取り込む。

main.m から以下のように使う。

#import <Foundation/Foundation.h>
#import <TestFramework/TestFramework.h>

int main(int argc, const char * argv[]) {
    
    NSLog(@"%2d",twice(3));
    
    return 0;
}

走らせてみると・・

予想通り 6 と表示してくれました。

成功、成功。

rpath の設定

なお、設定にもよるのだが、こうして作成されたコマンドラインプログラムを(Xcode 上ではなくて)ターミナルから実行すると

Library not loaded: @rpath

というエラーが出る時がある。
このエラーは、コンパイル時の @rpath の設定が実際に生成されたプログラムが参照すべきライブラリ(今回は TestFramework)の path と一致していない時に発生する。

その場合は、Xcode の Runpath Search Paths あたりの設定を見直してください。

ここ、指定しておかないと framework の所定の置き場所しか見に行かないようです。

正しく設定できていれば、ターミナル上からも

と Xcode 環境で走らせた時と同様の結果になります。