概要
@pe-taさんの「やっとわかったSwift/CoreData入門」をObjective-Cに移植したものです。(逐語訳ではありません。自己流のコードだらけです。)マックブック用ですから、よかったらアップルのDeveloperアカウントを持ってない方も使ってみてください。
バージョンはmacOSが12.3、Xcodeが13.3です。
プロジェクトの作成
Xcode→File→NewProjectとし、最上段をmacOS(iOSではない)とし、APPを指定します。次のページに移って、プロダクト名は原作と同じTestCoreData、言語はObjective-Cにします。UseCoreDataのチェックをお忘れなく。
チーム名などはNoneのままでOKです。
Entityの設定
原作と全く同じで、新しいEntityを作り、名前をMonsterとします。そのAttributeは一つだけで、NameはmonsterName、TypeはStringとします。
EntityのMonsterを選択し、右側のCodegenというところがClass Definitionになっているのを確認して下さい。多分デフォールトでそうなっている筈です。
ViewController.m
ちょっと乱暴ですが、元のコードをすべて削り、代わりに次のコードをこのままペーストして下さい。これをやらないと、次の作業の間ずっとXcodeが文句をいってきます。//
// ViewController.m
// TestCoreData
//
// Created by Myself on 2022/03/16.
//
#import "ViewController.h"
ViewController *viewctrl_ptr; /* (referenced by 'AppDelegate') */
@implementation ViewController
{
#pragma mark global variables within ViewController
AppDelegate *parent_ptr;
NSArray<NSManagedObject *> *monstary; /* (handled by Core Data) */
NSManagedObjectModel *my_mom;
NSPersistentStoreCoordinator *my_psc;
NSManagedObjectContext *my_moc;
NSFetchRequest *my_fetch;
}
#pragma mark initializers
- (void)viewDidLoad
{ [super viewDidLoad];
viewctrl_ptr = self; /* set public variable */
tbl_view_ptr.delegate = self;
tbl_view_ptr.dataSource = self;
monstary = nil; /* (against some accident) */
} // viewDidLoad
- (void)setRepresentedObject:(id)representedObject
{ [super setRepresentedObject:representedObject];
// Update the view, if already loaded.
} // setRepresentedObject
#pragma mark public functions
- (void)setParent_ptr:(AppDelegate *)myparent
/* called by 'AppDelegate' */
{ parent_ptr = myparent;
NSLog(@"parent ready");
} // setParent_ptr
-(void)set_pc
/* set persistent-container variables */
/* called by 'AppDelegate' */
/* called after AppDelegate ready */
{ [self initcore]; /* initialize core data */
[tbl_view_ptr reloadData]; /* display registered Monsters */
} // set_pc
#pragma mark event handler
- (IBAction)editdone:(NSTextField *)sender
{ NSString *mystring;
NSLog(@"notification from text-field");
[txtfield_ptr resignFirstResponder];
mystring = txtfield_ptr.stringValue;
if ( mystring == nil )
NSLog(@"textfield not yet ready");
else if ( mystring.length == 0 )
NSLog(@"textfield empty");
else
{ [self createOneWithName: mystring]; /* create one Monster */
[tbl_view_ptr reloadData];
txtfield_ptr.stringValue = @""; /* clear textfield */
}
} // editdone
#pragma mark core data functions
- (void)initcore
/* initialize core data */
/* note: 'my_mom' and 'my_psc' not used */
{ NSArray *temp_ary;
NSError *errorptr;
NSAssert(parent_ptr.persistentContainer != nil, @"persistentContainer failed");
my_mom = parent_ptr.persistentContainer.managedObjectModel;
NSAssert(my_mom != nil, @"managed object managedobjectmode failed");
my_psc = parent_ptr.persistentContainer.persistentStoreCoordinator;
NSAssert(my_psc != nil, @"persistent store coordinator failed");
my_moc = parent_ptr.persistentContainer.viewContext;
NSAssert(my_moc != nil, @"managed object context failed");
my_fetch = [[NSFetchRequest alloc] initWithEntityName: @"Monster"];
errorptr = nil; /* (unnecessary ... result not used) */
temp_ary = [my_moc executeFetchRequest: my_fetch error: &errorptr];
if ( temp_ary == nil )
NSLog(@"monster array not yet ready");
else /* (usually array ready) */
monstary = temp_ary;
} // initcore
- (void)createOneWithName: (NSString *)the_name
/* create and save one entity */
{ NSManagedObject *one_monster;
NSEntityDescription *myentity;
NSError *errorptr;
BOOL myresult;
/* create one Monster(entity) */
NSLog(@"start creating Monster");
myentity = [NSEntityDescription entityForName:@"Monster"
inManagedObjectContext: my_moc];
one_monster = [[NSManagedObject alloc]
initWithEntity: myentity
insertIntoManagedObjectContext: my_moc];
NSAssert(one_monster != nil, @"managed object creation failed");
/* set attribute (Monster name) */
[one_monster setValue: the_name forKey: @"monsterName"];
/* save the Monster */
/* update monster-array */
NSLog(@"start saving monster");
errorptr = nil; /* (&errorptr = '\0') */
myresult = [one_monster.managedObjectContext save: &errorptr];
if ( ( myresult == NO ) || ( errorptr != nil ) )
NSLog(@"monster '%@' save error", the_name);
else /* (here errorptr == nil) */
{ NSLog(@"new monster with name '%@' saved", the_name);
monstary = [my_moc executeFetchRequest: my_fetch error: &errorptr];
NSAssert(monstary != nil, @"monster-array fetch failed");
}
} // createOne
#pragma mark delegate and datasource
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{ NSInteger nbr_rows; /* number of rows */
if ( monstary == nil ) /* monster array not ready */
nbr_rows = 0;
else
nbr_rows = monstary.count;
return nbr_rows;
} // numberOfRows
- (id)tableView:(NSTableView *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(NSInteger)rowIndex
{ NSString *mystring;
NSManagedObject *one_monster;
if ( monstary == nil ) /* array not ready (never occurs?) */
mystring = @"";
else
{ one_monster = [monstary objectAtIndex: rowIndex];
mystring = [one_monster valueForKey: @"monsterName"];
}
return mystring;
} // objectValue
@end
Xcodeはまだ何か文句をいっていますが、先に進みましょう。
ViewController.h
変更は少しだけです。
#import <CoreData/CoreData.h> // ← これを追加する
#import "AppDelegate.h" // ← これを追加する
@interface ViewController : NSViewController
<NSTableViewDelegate, NSTableViewDataSource> // ← これを追加する
/* -- public functions (called by 'AppDelegate') -- */
// AppDelegateから呼ばれるfunctionを2行追加
- (void)setParent_ptr:(AppDelegate *)myparent;
- (void)set_pc;
AppDelegate.m
変更は次のとおりです。
#import "AppDelegate.h"
#import "ViewController.h" // ← これを追加する
extern ViewController *viewctrl_ptr; // ← これを追加する
@interface AppDelegate ()
- (IBAction)saveAction:(id)sender;
@end
@implementation AppDelegate
// この関数の内容を次のようにする
// この関数はViewDidLoadよりも後で呼ばれる
// だからviewctrl_ptrはすでにセットされている筈
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{ NSAssert(viewctrl_ptr != nil, @"ViewController not ready");
[viewctrl_ptr setParent_ptr: self];
[viewctrl_ptr set_pc];
// Insert code here to initialize your application
}
AppDelegate.hの変更はありません。
(投稿直前になって気が付いたのですが、viewctrl_ptrの初期値をnilにしておくべきでした。このままではNSAssertの意味がありませんよね 。)
ストーリーボードの設定
Viewに部品TextFieldをセット(ドラッグ・ドロップ)します。
TextFieldのActionがSent On End Editingになっているのを確認します。
TextFieldの下に部品TableViewをセットします。
TableViewのContent ModeをCell Based、列数を1にします。
TableViewにはTextCellと表示されていますが、ここにTextFieldCellをセットします。似たような部品が多いのでご注意を。
ここから3本、コネクションをやります。アシスタントエディタを開き、ViewController.mを表示させます。
TextFieldからコントロール・ドラッグして、ViewController.mの@implementationのすぐ下のブレースの中にIBOutletを作り、名前をtxtfield_ptrにします。
TableViewからコントロール・ドラッグして、さっきの下にIBOutletを作り、名前をtbl_view_ptrにします。
最後にTextFieldからコントロール・ドラッグして、ViewController.mの中程にある(IBAction)editdoneにコネクトします。
なおdelegateとdatasourceはViewController.mで手当てしているので、処理は不要です。
試運転
ビルドさせて実行し、テキストフィールドにモンスター名を入力してエンターキーを押すと、テーブルに名前が表示されます。デバッグウィンドウで作業内容がわかりますね。
試運転が終わったらストップボタンを押して下さい。
アプリケーションを作成
Xcode→Product→Archiveに進むと、アーカイブが作成されます。作成後の表示を見失ったらXcode→Window→Organizerで同じ画面が表示されます。
Distribute Appからcopy Appとし、お好きなフォルダーを選んで下さい。Finderツールを使って見ると堂々たる(?)自作アプリケーションの完成です。拡張子もちゃんとappになっていますね。
Xcodeを終了し、Finderツールでアプリケーションをダブルクリックすると、Xcodeなしでアプリが起動し、メニューバーにはアプリ名が表示されます。
モンスター名をいくつか追加し、メニューバーからアプリを終了させ、再び起動させると、追加分も正しく表示されているのがわかります。