『MAC OS X COCOA プログラミング(原著第4版)』お勉強覚え書き

今週は、せっかくMacbook Airを購入したことですし、ということでObjective-Cのお勉強をしております。特になにかを作ろうといった意図もなく、興味本意で横道に逸れております。教本は『MAC OS X COCOA プログラミング(原著第4版)/アーロン・ヒレガス他』です。訳が良いのか、親切でわかりやすい本ですね。というか、本屋さんで選ぼうにもOSXのCocoaプログラミングに関する日本語書籍はこれしか選びようがないようです。これ読み終わった後はどうしたものかと不安です。

現在、第8章を読み終えんとしているところですが、「もっと詳しく:NSArrayControllerを使用しない並び替え」でちょっとハマったので覚え書きしておきます。
全体像については同書を読んでいただくとして、この本では単にNSMutableArrayがある場合は、

-(void)tableView:(NSTableView*)tableView sortDiscriptorsDidChange:(NSArray*)oldDiscriptors {
    NSArray* newDescriptors = [tableView sortDescriptors];
    [myArray sortUsingDescriptors:newDescriptors];
    [tableView reloadData];
}

「これでアプリケーションに並べ替え機能が実装されました」とあるのですが、これだけでは並べ替えられません。試しに第6章で作成したSpeakLineアプリケーションで、NSArrayをNSMutabeArrayに変更し、上記コードのようなものを追加してみてもそれだけでは動作しません。ここまで注意深く同書を読んでいればある程度推測がつくことなのでしょうが、しばし手が止まってしまいました。うーん、まずTable ColumnにSort Keyを設定する必要があります。そして、Sort Keyを設定するためには当然キー項目がなければなりません。ということはキーバリューコーディングのところでやったキーなるものが必要。ということはNSMutableArrayの要素はvalueForKey:が使えるオブジェクトでなければいけない。ということで、次のような修正をしました。要するに、Voiceクラスを追加し、そのインスタンスをNSMutableArrayの要素にしただけです。
SpeakLineAppDelegate.h

@interface SpeakLineAppDelegate : NSObject <NSApplicationDelegate, NSSpeechSynthesizerDelegate, NSTableViewDataSource, NSTableViewDelegate> {
    //NSArray* _voices;
    NSMutableArray* _voices;
// 以下省略

SpeakLineAppDelegate.m

// ここから追加
@interface Voice : NSObject
@property (copy) NSString* name;
@property (copy) NSString* voice;
- (id)initWithName:(NSString*)name voice:(NSString*)voice;
- (id)initWithName:(NSString *)name;
- (id)init;
@end

@implementation Voice
- (id)initWithName:(NSString*)name voice:(NSString*)voice {
    if (self = [super init]) {
        self.name = name;
        self website link.voice = voice;
    }
    return self;
}
- (id)initWithName:(NSString *)name {
    return [self initWithName:name voice:nil];
}
- (id)init {
    return [self initWithName:nil];
}
@end
// 追加ここまで

// 中略

- (id)init {
    
    self = [super init];
    if (!self) return nil;
    NSLog(@"init");
    
    _speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
    _speechSynth.delegate = self;
    
    //_voices = [NSSpeechSynthesizer availableVoices];
    NSArray* voices = [NSSpeechSynthesizer availableVoices];
    _voices = [NSMutableArray arrayWithCapacity:[voices count]];
    for (NSString* v in voices) {
        NSDictionary* dict = [NSSpeechSynthesizer attributesForVoice:v];
        Voice* voice = [[Voice alloc] initWithName:[dict objectForKey:NSVoiceName] voice:v];
        [_voices addObject:voice];
    }
    
    return self;
}

- (void)awakeFromNib {
    NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice];
    //NSInteger defaultRow = [_voices indexOfObject:defaultVoice];
    NSInteger defaultRow = [_voices indexOfObjectPassingTest:^BOOL(Voice* voice, NSUInteger index, BOOL* stop) {
        if ([voice.voice isEqualToString:defaultVoice]) {
            *stop = YES;
            return YES;
        }
        return NO;
    }];
    NSIndexSet* indices = [NSIndexSet indexSetWithIndex:defaultRow];
    [_tableView selectRowIndexes:indices byExtendingSelection:NO];
    [_tableView scrollRowToVisible:defaultRow];
}

// 中略

-(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    //NSString* v = [_voices objectAtIndex:row];
    //NSDictionary* dict = [NSSpeechSynthesizer attributesForVoice:v];
    //return [dict objectForKey:NSVoiceName];
    return [[_voices objectAtIndex:row] name];
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification {
    NSInteger row = [_tableView selectedRow];
    if (row == -1) {
        return;
    }
    //NSString* selectedVoice = [_voices objectAtIndex:row];
    NSString* selectedVoice = [[_voices objectAtIndex:row] voice];
    _speechSynth.voice = selectedVoice;
    NSLog(@"new voice = %@", selectedVoice);
}

- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors {
    NSLog(@"tableView:sortDesciptorsDidChange:");
    [_voices sortUsingDescriptors:[tableView sortDescriptors]];
    [tableView reloadData];
}

@end

そして、次のようにInterface BuilderでTable Columnを選択し、Attributes InspectorでSort Keyを設定します。
スクリーンショット_2013-08-02_22.09.25
これでどうやら並び替えができるようになりました。