Xcode(2回目) [Xcode]
次へ進む前に、アップルのイベントを操作する上で体に身につけなければならないコマンドの操作に付いて一言。
ウインドウズからマックに転向する人達が最初に躓くのが、キーボードの配列の違いとコマンド操作ではないでしょうか。キーボードは、マックの方がキーが少なく単純です。メインはコマンド(ஐ)+オプションまたはシフト又はコントロールの組み合わせであり、これだけでかなりの操作が出来ます。英語、日本語の切り替えはワンボタンであり漢字カタカナひらがなは、スペースバーで選択でき、かなりの確率で変換してくれます。
私がよく使うのは、コマンド+(W, S,Shift+S, C ,V, N, O, I, tab, option+esc, option+取り出しマーク)こんな。もんでしょうか。
Xcodeの中で使うのが、コマンド+(Shift+R, R, C, V) 、マウスの方が操作が早い場合はマウスと言う風に使っています。又コードはかなりの補完機能が有りますから、慣れると大したキーを打つ必要が有りません。例えば、@proと打つと@property()かどうかハイライト表示になります。良ければ、tabキーを押せば次の補完に移ります。特にこれは長たらしい構文の時には役に立ちます。他のIDEも今では大概そうでしょうけれども、Objective-Cの場合特に役に立ちます。また、構文が間違っていれば、色がつくはずが付かないのですぐ分かりますし、また、), }, ]が対となる位置にこないときは、何かが抜けている事を教えてくれます。
取りあえずこれくらいにして、コードです。
Mountain.h |
// // Mountain.h // Mountain // // on 11/07/19. // Copyright 2011 No Company. All rights reserved. // #import <Cocoa/Cocoa.h> #define kMountainNameString @"name" #define kMountainHeightString @"height" #define kMountainClimbedDateString @"climbedDate" @interface Mountain : NSObject {
NSString *_name; NSNumber *_height; NSDate *_climbedDate; } //三つのデータメンバーにアクセスする同期プロパティ @property(copy) NSString *name; @property(copy) NSNumber *height; @property(retain) NSDate *climbedDate; //ローカライズplistから探した辞書からオブジェクトを作成 + (Mountain *)mountainWithDictionary:(id)inputDictionary; //デザイン化されたイニシャライザー - (id)initWithName:(NSString *)name height:(NSNumber *)height climbedDate:(NSDate *)climbedDate; //ここがObjective-Cのすご技です。型が違うのに三つをまとめて初期化できます。 //解説(判断の手がかりとなるために典型的に使われる) - (NSString *)description; - (NSString *)descriptionWithLocale:(id)locale; @end |
Mountain.m |
// // Mountain.m // Mountain // // on 11/07/19. // Copyright 2011 No Company. All rights reserved. // #import "Mountain.h" #import "MountainsController.h" @implementation Mountain //データメンバーへの同期アクセス @synthesize name = _name; @synthesize height = _height; @synthesize climbedDate = _climbedDate; + (Mountain *)mountainWithDictionary:(id)inputDictionary { id returnValue = nil; if (inputDictionary != nil && [inputDictionary isKindOfClass:[NSDictionary class]]) { NSString *name = [(NSDictionary *)inputDictionary objectForKey:kMountainNameString]; NSNumber *height = [(NSDictionary *)inputDictionary objectForKey:kMountainHeightString]; NSDate *climbedDate = [(NSDictionary *)inputDictionary objectForKey:kMountainClimbedDateString]; if (name != nil && height != nil) { returnValue = [[[ Mountain alloc] initWithName:name height:height climbedDate:climbedDate] autorelease]; } } return returnValue; } - (id)initWithName:(NSString *)name height:(NSNumber *)height ClimbedDate:(NSDate *)climbedDate { self = [super init]; if (self != nil) { self.name = name; self.height = height; self.climbedDate = climbedDate; } return self; } - (void)dealloc { [_name release]; [_height release]; [_climbedDate release]; [super dealloc]; } //解説 - (NSString *)description { return [self descriptionWithLocale:[NSLocale autoupdatingCurrentLocale]]; } - (NSString *)descriptionWithLocale:(id)locale { NSString *returnValue = @""; if (self.climbedDate != nil && locale != nil) { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateStyle:NSDateFormatterShortStyle]; [formatter setTimeStyle:NSDateFormatterNoStyle]; [formatter setLocale:locale]; returnValue = [NSString stringWithFormat:@"%@-%@-%@", self.name, self.height, [formatter stringFromDate:self.climbedDate]]; [formatter release]; } else { returnValue = [NSString stringWithFormat:@"%@-%@-%@", self.name, self.height]; } return returnValue; } @end |
MountainsController.h |
// // MountainsController.h // Mountains // // on 11/07/19. // Copyright 2011 No Company. All rights reserved. // #import <Cocoa/Cocoa.h> @interface MountainsController : NSWindowController {
IBOutlet NSTextField *sentenceText; IBOutlet NSTableView *summaryTable; IBOutlet NSPopUpButton *calendarPopup; IBOutlet NSDatePicker *datePicker;
@private
NSArray *_mountains; NSTimer *_timer; } //カレンダーを変える - (IBAction)changeCalendar:(id)sender; //テーブルビューデータソースメソッド - (NSInteger)numberOfLowsInTableView:(NSTableView *)aTableView; - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex; //ソートキーが送られたときのデリゲートメソッド - (void)tableView:(NSTableView *) didClickTableColumn:(NSTableColumn *)tableColumn; //通知を受け取ったときのテーブルビューへの返答 - (void)tableSelectionChanged:(id)notification; - (void)localeChanged:(id)notification; @end |
MountainsController.m |
// // MountainsController.m // Mountains // // on 11/07/19. // Copyright 2011 No Company. All rights reserved. // #import "Mountain.h" #import "MountainsController.h" //ポップアップした時にそれぞれに与える索引 enum { kGregorianCalendarItem = 2, kBuddhistCalendarItem , kHebrewCalendarItem, kIslamicCalendarItem, kIslamicCivilCalendarItem, kJapaneseCalendarItem }; @interface MountainsController (PrivateFunctions) //NSLocaleでは用意してないので、即席関数 - (NSLocale *)localeLanguageComboWithCalendar; //同じくローカルカレンダーかユーザーがオーバーライドしたときの関数 - (NSCalendar *)calendar; //ソートしたときの山データの配列 - (NSArray *)mountains; - (NSArray *)sortedMountains; //アイテムリセットを上用する - (void)resetSentence; - (void)resetAll; - (void)updateDatePicker:(NSTimer *)timer; //? - (NSString *)heightAsString:(NSNumber *)heightNumber; - (NSString *)dateAsString:(NSDate *)rawDate; @end @implementation MountainsController - (id)init { self = [super init]; if (self != nil) { //通知のためのオブザーバーをnilにセット [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(localeChanged:) name:NSCurrentLocaleDidChangeNotification object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self];
if (_timer != nil) { [_timer invalidate]; [_timer release]; }
[_mountains release]; [super dealloc]; } - (void)awakeFromNib { [self resetSentence];
// Adjust the size of the text in the table view for easier viewing [ summaryTable setRowHeight:( [ summaryTable rowHeight ] * 18.0 / [ NSFont systemFontSize ] ) ]; NSFont *font = [ NSFont systemFontOfSize:18.0 ]; for ( NSTableColumn *column in [ summaryTable tableColumns ] ) { NSCell *cell = [ column dataCell ]; if ( [ cell isKindOfClass:[ NSCell class ] ] && [ cell type ] == NSTextCellType ) { [ cell setFont:font ]; } }
// Now that summaryTable is available, we can start getting notifications about it [ [ NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(tableSelectionChanged:) name:NSTableViewSelectionDidChangeNotification object:summaryTable ];
// We use a timer so that our date-picker (which shows the time) // updates every second _timer = [ [ NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateDatePicker:) userInfo:nil repeats:YES ] retain ]; [ self updateDatePicker:_timer ];
} // Our popup calls this to override the selected calendar - (IBAction)changeCalendar:(id)sender { [ self resetAll ]; } // Table view data source functions - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { return [ [ self mountains ] count ]; } - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { id returnValue = @""; Mountain *mountain = [ [ self sortedMountains ] objectAtIndex:rowIndex ]; NSString *columnID = [ aTableColumn identifier ]; if ( [ columnID isEqualToString:kMountainNameString ] ) { returnValue = [ [ mountain name ] capitalizedString ]; } else if ( [ columnID isEqualToString:kMountainHeightString ] ) { returnValue = [ self heightAsString:[ mountain height ] ]; } else if ( [ columnID isEqualToString:kMountainClimbedDateString ] ) { returnValue = [ self dateAsString:[ mountain climbedDate ] ]; } return returnValue; } // When the table view sorting changes, the selected row doesn't, so we // have to reset our sentence display to match the new data on that row - (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn { [ self resetSentence ]; } - (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray *)oldDescriptors { [ aTableView reloadData ]; } // Notification target functions - (void)tableSelectionChanged:(id)notification { [ self resetSentence ]; } - (void)localeChanged:(id)notification {
NSLocale *locale = [ self localeLanguageComboWithCalendar ]; NSLog( @"The locale has changed, the new calendar identifier is %@", [ locale displayNameForKey:NSLocaleCalendar value:[ [ self calendar ] calendarIdentifier ] ] ); NSLog( @"The new calendar is %@", [ locale displayNameForKey:NSLocaleCalendar value:[ self calendar ] ] );
[ self resetAll ]; } // NSApp delegate method - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { return YES; } @end @implementation MountainsController (PrivateFunctions) // We don't cache this - (NSCalendar *)calendar { NSCalendar *returnValue = nil; // We rely on the fact that all the localizations use the same popup for overriding // the calendar setting switch ( [ calendarPopup indexOfSelectedItem ] ) { case kGregorianCalendarItem: returnValue = [ [ [ NSCalendar alloc ] initWithCalendarIdentifier:NSGregorianCalendar ] autorelease ]; break; case kBuddhistCalendarItem: returnValue = [ [ [ NSCalendar alloc ] initWithCalendarIdentifier:NSBuddhistCalendar ] autorelease ]; break; case kHebrewCalendarItem: returnValue = [ [ [ NSCalendar alloc ] initWithCalendarIdentifier:NSHebrewCalendar ] autorelease ]; break; case kIslamicCalendarItem: returnValue = [ [ [ NSCalendar alloc ] initWithCalendarIdentifier:NSIslamicCalendar ] autorelease ]; break; case kIslamicCivilCalendarItem: returnValue = [ [ [ NSCalendar alloc ] initWithCalendarIdentifier:NSIslamicCivilCalendar ] autorelease ]; break; case kJapaneseCalendarItem: returnValue = [ [ [ NSCalendar alloc ] initWithCalendarIdentifier:NSJapaneseCalendar ] autorelease ]; break; default: // Always include an explicit default case in a switch statement... break;
} return returnValue;
} // Our array of Mountain data // We allocate this lazily, waiting until we need it before we read it in - (NSArray *)mountains { if ( _mountains == nil ) { // Get the correct, localized version of the data file NSString *path = [ [ NSBundle mainBundle ] pathForResource:@"Mountains" ofType:@"plist" ]; NSArray *mountainList = ( path != nil ? [ NSArray arrayWithContentsOfFile:path ] : nil ); NSMutableArray *array = [ NSMutableArray arrayWithCapacity:( mountainList != nil ? [ mountainList count ] : 0 ) ]; for ( NSDictionary *mountainDict in mountainList ) { // Create a Mountain object from each entry in the plist and add it [ array addObject:[ Mountain mountainWithDictionary:mountainDict ] ]; } // Just to be perverse, we copy our mutable array rather than keeping it // but not using its mutability _mountains = [ [ NSArray alloc ] initWithArray:array ]; } return _mountains; } // Our array of mountains sorted per the table view's descriptors - (NSArray*)sortedMountains { return [ [ self mountains ] sortedArrayUsingDescriptors:[ summaryTable sortDescriptors ] ]; } // Reset the text field with a sentence for the currently selected mountain // This is localized, and two versions are used since not all mountains have // a climbed date available - (void)resetSentence { NSString *sentence = @""; NSString *format; if ( [ summaryTable selectedRow ] != -1 ) { Mountain *mountain = (Mountain *) [ [ self sortedMountains ] objectAtIndex:[ summaryTable selectedRow ] ]; if ( mountain.climbedDate != nil ) { format = NSLocalizedStringFromTable( @"sentenceFormat", @"Mountains", @"A sentence with the mountain's name (first parameter), height (second parameter), and climbed date (third parameter)" ); sentence = [ NSString stringWithFormat:format, mountain.name, [ self heightAsString:mountain.height ], [ self dateAsString:mountain.climbedDate ] ]; } else { format = NSLocalizedStringFromTable( @"undatedSentenceFormat", @"Mountains", @"A sentence with the mountain's name (first parameter), and height (second parameter), but no climbed date" ); sentence = [ NSString stringWithFormat:format, mountain.name, [ self heightAsString:mountain.height ] ]; } } [ sentenceText setStringValue:sentence ]; } // Update all our UI elements - (void)resetAll { [ self resetSentence ]; [ summaryTable reloadData ]; [ self updateDatePicker:_timer ]; } // Make sure our date picker gets the correct, localized calendar - (void)updateDatePicker:(NSTimer*)timer { [ datePicker setLocale:[ self localeLanguageComboWithCalendar ] ]; [ datePicker setCalendar:[ self calendar ] ]; [ datePicker setDateValue:[ NSDate date ] ];
} // We want a single string expressing a mountain's height // We need to allow for the possibility that the user is using either metric // or non-metric units. If the units are non-metric, we need to do the // conversion ourselves - (NSString*)heightAsString:(NSNumber*)heightNumber { NSString *returnValue = @""; if ( heightNumber != nil ) { NSString *format = @"%d"; NSInteger height = [ heightNumber integerValue ]; NSNumber *usesMetricSystem = [ [ NSLocale autoupdatingCurrentLocale ] objectForKey:NSLocaleUsesMetricSystem ]; if ( usesMetricSystem != nil && ![ usesMetricSystem boolValue ] ) { // Convert the height to feet height = (int) ( (float) height * 3.280839895 ); format = NSLocalizedStringFromTable( @"footFormat", @"Mountains", @"Use to express a height in feet" ); } else { format = NSLocalizedStringFromTable( @"meterFormat", @"Mountains", @"Use to express a height in meters" ); }
NSNumberFormatter *formatter = [ [ NSNumberFormatter alloc ] init ]; [ formatter setNumberStyle:NSNumberFormatterDecimalStyle ];
returnValue = [ NSString stringWithFormat:format, [ formatter stringFromNumber:[ NSNumber numberWithInteger:height ] ] ];
[ formatter release ]; } return returnValue; } // A single string expressing a mountain's climbed date, properly localized - (NSString*)dateAsString:(NSDate*)date { NSString *returnValue = @""; if ( date != nil ) { NSDateFormatter *formatter = [ [ NSDateFormatter alloc ] init ]; [ formatter setDateStyle:NSDateFormatterMediumStyle ]; [ formatter setTimeStyle:NSDateFormatterNoStyle ]; [ formatter setLocale:[ self localeLanguageComboWithCalendar ] ]; returnValue = [ formatter stringFromDate:date ]; [ formatter release ]; } // We leave this in just to demonstrate that descriptionWithLocale does the right thing NSLog( @"%@ => %@", [ date descriptionWithLocale:[ self localeLanguageComboWithCalendar ] ], returnValue ); return returnValue; } // A convenience function, since NSLocale doesn't provide this for us - (NSLocale*)localeLanguageComboWithCalendar { // This is tricky. We need to create a new locale, one which is identical to the // current locale except that we explicitly override the language and calendar. We can then // use this new locale to create a date formatter, which will then // generate the proper date for us, as well as other objects
NSCalendar *calendar = [ self calendar ]; NSArray *languages = [ NSLocale preferredLanguages ]; NSString *languageIdentifier = ( languages != nil ? [ languages objectAtIndex:0 ] : nil ); NSString *localeLanguage = [ [ NSLocale autoupdatingCurrentLocale ] objectForKey:NSLocaleLanguageCode ]; NSString *localeIdentifier = [ [ NSLocale autoupdatingCurrentLocale ] localeIdentifier ]; NSString *newLocaleIdentifier = localeIdentifier;
if ( languageIdentifier != nil && localeLanguage != nil && ![ languageIdentifier isEqualToString:localeLanguage ] ) { newLocaleIdentifier = [ NSString stringWithFormat:@"%@-%@", languageIdentifier, localeIdentifier ]; } if ( calendar != nil ) { NSString *calendarIdentifier = [ calendar calendarIdentifier ]; newLocaleIdentifier = [ NSLocale canonicalLocaleIdentifierFromString:[ NSString stringWithFormat:@"%@@calendar=%@", newLocaleIdentifier, calendarIdentifier ] ]; } NSLocale *returnValue = [ [ [ NSLocale alloc ] initWithLocaleIdentifier:newLocaleIdentifier ] autorelease ]; return returnValue; } @end |
省略可能な関数は省きました。また、英語のコメントが残っているところは、疲れたのでコピーペーストしただけです。取り合えずxibファイルを編集しないでビルドしてみます。最初はケアレスミスで39個のエラーが出て、直せる範囲で直しましたが、何故か5つの警告が消えません。中身がこうです。
プロジェクト Mountains の ビルド Mountains(構成 Debug) /Users///Mountains/Mountains/Mountain.m:76: warning: incomplete implementation of class 'Mountain' /Users///Mountains/Mountains/Mountain.m:76: warning: method definition for '-initWithName:height:climbedDate:' not found /Users////Mountains/MountainsController.m:157: warning: incomplete implementation of class 'MountainsController' /Users///Mountains/Mountains/MountainsController.m:157: warning: method definition for '-tableView::' not found /Users////Mountains/MountainsController.m:157: warning: method definition for '-numberOfLowsInTableView:' not found |
こうなると間違いを探すのが大変ですが、コンパイルは出来るのでxibファイルを先に編集します。
一応苦労して配置したつもりなのですが、警告の通りウインドウが出て来ません。なので、Mountain.mを良く見てみました。警告が出たinitWithName関数をよく見ると、climbedDateとしなければならないところ、ClimbedDateとしてしまったので、巧く行かなかったようです。早速直して実行しましたが結果はご覧の通りです。上部のコードは直していないので時間があったら探してみて下さい。
このmeterFormatって何でしょうか。XXX YYY ZZZには何故ストリングが受信できないのでしょうか。次回に続く。
コメント 0