addsubview するときの view のお手軽な作成方法

文法ラウンドやメソッド暗記モードを経て、cocoa でアプリっぽいものを作成する機会が多くなったが、全然知られていない方法を一つ。

みんな大好き NSView だが、子供の view を作成して親 view に addsubview したいというときがしばしばある。

しかし、具体的な方法論がほとんど知られていない(笑)。

ワイは試行錯誤(といってもさほど時間はかからなかったが)の結果、以下のようにしている。

Xcode の + で UI パーツのライブラリを呼び出すダイアログを呼び出して、custom view をチョイス。

ここがキモだと思うのだが、これを親ビューが所属する Controller Scene に置く

すると上図のように Xcode 上でもしっかりと操作可能な形で配置される。

iOS 関係はわからんが、MacOS でこんな図は見たことがない。

追加した view を ChildView、元々あった view を ParentView とすると、ViewController の viewdidload あたりに

    _childview = [[ChildView alloc] initWithFrame:_parentview.frame];
    [_parentview addSubview:_childview];

としておくと、しっかりこのビューが生成される。

かなり便利な方法だと思うんだが、全然知られてないよね。

NSView の再描画

ところで MacOS の描画関係は Metal から入ったワイは、NSView は自動で再描画してくれるものだと思い込んでいたが、違うらしい。setNeedsDisplay メソッドで明示する。

 

Core Plot

これも今すぐどうこうという話ではないのだが、Core Plot という iOS/MacOS のグラフ・チャートライブラリを見つけたのでメモ。

公式リポジトリはこちら

arm Mac で使いたければ、release-2.4 のブランチを使う。

git clone -b release-2.4  git@github.com:core-plot/core-plot core-plot24

で落として、framework フォルダ内の .xocdeproj を Xcode で起動。

シェルスクリプトに不具合があるようだが、所定のファイルはできているようなので、この状態で本体自体をビルド。

CorePlot.framework が生成されるので、これを使いたいプロジェクトに取り込んで使用する。

この記事のサンプルを MacOS でビルド。

できてますね。
ソースコードは若干修正して以下のようになる。

#import <Cocoa/Cocoa.h>
#import <CorePlot/CorePlot.h>

@interface ViewController : NSViewController<CPTPieChartDataSource,CPTPieChartDelegate>

@property (readwrite, nonatomic) NSMutableArray *pieChartData;

@end

#import "ViewController.h"

@implementation ViewController

@synthesize pieChartData;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // ホスティングビューを生成します。
    CPTGraphHostingView *hostingView = [[CPTGraphHostingView alloc]
                                        initWithFrame:CGRectMake(0, 0, 320, 320)];
    
    // グラフを生成します。
    CPTXYGraph *graph = [[CPTXYGraph alloc] initWithFrame:hostingView.bounds];
    hostingView.hostedGraph = graph;
    
    // 今回は円グラフなので、グラフの軸は使用しません。
    graph.axisSet = nil;
    
    // 円グラフのインスタンスを生成します。
    CPTPieChart *pieChart = [[CPTPieChart alloc] init];
    
    // 円グラフの半径を設定します。
    pieChart.pieRadius = 80.0;
    
    // データソースを設定します。
    pieChart.dataSource = self;
    
    // デリゲートを設定します。
    pieChart.delegate = self;
    
    // グラフに円グラフを追加します。
    [graph addPlot:pieChart];
    
    // グラフに表示するデータを生成します。
    self.pieChartData = [NSMutableArray arrayWithObjects:
                         [NSNumber numberWithDouble:40.0],
                         [NSNumber numberWithDouble:30.0],
                         [NSNumber numberWithDouble:20.0],
                         [NSNumber numberWithDouble:10.0],
                         nil];
    
    // 画面にホスティングビューを追加します。
    [self.view addSubview:hostingView];
}




// グラフに使用するデータの数を返すように実装します。
-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
{
    return [self.pieChartData count];
}

// グラフに使用するデータの値を返すように実装します。
-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    return [self.pieChartData objectAtIndex:index];
}
@end

です。

 

NSSplitView

今すぐどうこうというわけではないが、NSSplitView を使えるとアプリのウィンドウの表現が豊かになりそうなので、ちょっと調べる。

ネット上で使えそうなリソースは・・・

How to Use NSSplitView in a macOS app

まあ、良い記事ではあると思うけど、ちょっと応用的でしょうか。

動画もあったが、現代的ではない。

いつもの通り、公式ページもチェック。

 

結局、自分で手を動かしてまあまあ使えるようになった。

上半分は Metal の View で下半分が NSView を Core Graphics で描画したものです。
NSSplitVewDelegate プロトコルを適用するところで、おかしな挙動するんじゃないかと思ってましたが、そんなことはなく。結果オーライということで。

静止画だと伝わらないですが、ディバイダで上下の比率を変えることができます。

今回はちょっと楽して ViewController に NSSplitVewDelegate プロトコルを適用してます。

 

retain, release と -fno-objc-arc

これまで UI パーツの使い方を割と丁寧におさえてきたが、ここら辺で cocoa (最近だと AppKit というのかな?)アプリの作成に慣れるために、個人的に試験的なアプリを作成している。

ARC をオフにするには

その際に「これはいくらなんでも説明不足なんじゃ?」と思ったのが、ARC の使い方。

まず、「現在ではデフォルトで ARC がオンになっている」みたいなことは書かれているが、では、どうやって ARC をオフにしていいのか?そうすることのメリットは?みたいな説明はほとんどなされていない。

いやあ、これだと、スニペットみたいなコードは書けても、アプリを作成する際に色々と不都合が起こると思う。

その説明はいったんおいて、実用的なことを先に書くと、ARC = オンになっている Obj-C プロジェクトである部分だけをオフにしたいならば、TARGETS -> Build Phase -> Compile Sources でオフにしたいファイルの Compiler Flags に -fno-objc-arc というオプションを与える

これで、このファイルに含まれるクラスの ARC は無効になる。
あるオブジェクトを保持したければ retain (メソッド)を、破棄したければ release (メソッド)を明示する。

上の例では AppDelegate にこのオプションを与えたが、多分、これはかなり実用的な例。
ワイが試験的に作成しているアプリはまだ構造はさほど複雑ではないので、WindowsController は AppDelegate に集約している。
この時、困ったのは、あるウィンドウを閉じた時、(ARC をオンにしていると)そのウィンドウを管理しているコントローラーの存在が、不安定なように見えたこと。
ウィンドウをクローズするというのは、ウィンドウオブジェクトが破棄されたのか、それとも見えなくなっているだけでどこかにあるのかなどいくつか可能性があると思うが、この時の挙動がはっきりしない。

例えば、あるウィンドウのコントローラー(のポインタ)を wc としたとき

if(wc = nil){
  //処理
}

のようなコードを書いても、思ったような処理ができなかった。

だが、ARC をそこだけオフにしてみたところ、今の所狙ったようで動作している。

問題は使い分け

ARC に関してある程度把握はできたが、だからと言って、以降、すべて ARC=オフ にしてコーディングするかといえば、そんなことはないと思う。

まだ慣れていないせいもあると思うが、生成したすべてのオブジェクトで参照カウンタを気にするのは大変負担のように思える。

具体的な使い分けに関しては(この記事以外、ほとんど和文の資料はないと思うが)

retain と release の関係について

を参照してください。

 

NSTimer

ここしばらく Objective-C & MacOS の話題が続いているが、深い理由はない。

意外に使えることがわかってきて、この環境が好きになってきたから。

今回は、NSTimer の話。

結論から書くと、簡単に試すことができる。

タイマー関係といえば、昔、Java でえらい目にあったことがある。
動作がむちゃくちゃ不安定

まあ、Java の実行環境を考えるとしょうがないでしょう。

サンプル

それはともかく、検証に使ったソースコードは github にあげてあるのでよろしく。


@implementation Timer

-(id)initWithTimer
{
   self = [super init];
  if(self){
     NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0f
                                                       target:self
                                                     selector:@selector(doTask:)
                                                     userInfo:nil
                                                      repeats:YES];
     //[timer fire];
    }

     return self;
}

-(void)doTask:(NSTimer *)timer
{
   //[timer invalidate];
   NSLog(@"timer");
}

@end

コーディング自体は簡単。
上のように Timer クラスを作って、AppDelegate から呼び出すのみ。

これでタイマー処理ができてしまう。

実行すると所定の間隔で doTask メソッドを実行してくれます。