7.將數據顯示到表格視圖中
7.1. 問題
在一個使用表格視圖來給用戶呈現管理對象的程序裏,你想要讓獲取和呈現數據比手動管理數據更流暢些。
7.2. 方案
使用獲取結果控制器,它是 NSFetchedResultsController。
例子:
我們會使用視圖控制器來允許用戶添加新的 Person 對象到管理對象上下文:
1.創建使用CoreData的工程,CoreDataDemo2,並創建一個Person實體
2.你工程裏,創建兩個視圖控制器,兩個都是 UIViewController 的子類,分別命名爲PersonListViewController 和 AddPersonViewController。
3.現在打開程序委託的聲明並定義一個新的 PersonListViewController 類型的屬性並將它 命名爲 personListViewController。這將是我們要呈現給用戶的根視圖控制器。記住我們也需要一個導航控制器,所以讓我們也將他定義爲屬性吧:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) UINavigationController *navigationController;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
.m
#import "AppDelegate.h"
#import "PersonListViewController.h"
@interface AppDelegate ()
@property (nonatomic ,strong) PersonListViewController *personListViewController;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.personListViewController = [[PersonListViewController alloc]initWithNibName:nil bundle:nil];
self.navigationController = [[UINavigationController alloc]initWithRootViewController:_personListViewController];
return YES;
}
4、現在讓我們開始編譯程序的委託並同步視圖控制器和導航控制器屬性,並確保我們已經將 PersonListViewController.h 頭文件導入到程序的委託編譯文件裏,下面是我麼要初始化這個對象的一個實例。
5、當我們的程序加載後,我們想要呈現Person List View Controller到窗口,所以我們現在程序的委託 application:didFinishLaunchingWithOption:方法裏做文章吧:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface PersonListViewController : UIViewController<UITableViewDelegate,UITableViewDataSource,NSFetchedResultsControllerDelegate>
@property (nonatomic, strong) UITableView *tableViewPersons;
@property (nonatomic, strong) UIBarButtonItem *barButtonAddPerson;
@property (nonatomic, strong) NSFetchedResultsController *personsFRC;
@end
6、下面你需要實現一下 view controller,在 viewDidLoad 方法中,添加一個導航欄按 鈕。並實例化一個 table view,然後再添加一個編輯按鈕到導航欄中:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Person";
self.tableViewPersons = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableViewPersons.delegate = self;
self.tableViewPersons.dataSource = self;
[self.view addSubview:self.tableViewPersons];
self.barButtonAddPerson = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addNewPerson:)];
[self.navigationItem setRightBarButtonItem:self.barButtonAddPerson animated:NO];
}
7、很顯然,現在我們選擇了根視圖控制器變成桌面視圖的委託和數據資源。現在, 桌面上將會返回 0 個單元格。之後,當我們可能使用獲取結果控制器從管理對象上下文讀 取 Person 實體,我們可以 Person 對象的可能的數量:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
return nil;
}
- (void)addNewPerson:(id)paramSender{
AddPersonViewController *controller = [[AddPersonViewController alloc]initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
}
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "AppDelegate.h"
@interface AddPersonViewController : UIViewController
@property (nonatomic, strong) UITextField *textFieldFirstName;
@property (nonatomic, strong) UITextField *textFieldLastName;
@property (nonatomic, strong) UITextField *textFieldAge;
@property (nonatomic, strong) UIBarButtonItem *barButtonAdd;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"New Person";
CGRect textFieldRect = CGRectMake(20, 20, self.view.bounds.size.width - 40, 31);
self.textFieldFirstName = [[UITextField alloc]initWithFrame:textFieldRect];
self.textFieldFirstName.placeholder = @"First Name";
self.textFieldFirstName.borderStyle = UITextBorderStyleRoundedRect;
self.textFieldFirstName.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.textFieldFirstName.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
[self.view addSubview:self.textFieldFirstName];
textFieldRect.origin.y += 37.0f;
self.textFieldLastName = [[UITextField alloc] initWithFrame:textFieldRect];
self.textFieldLastName.placeholder = @"Last Name";
self.textFieldLastName.borderStyle = UITextBorderStyleRoundedRect;
self.textFieldLastName.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.textFieldLastName.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
[self.view addSubview:self.textFieldLastName];
textFieldRect.origin.y += 37.0f;
self.textFieldAge = [[UITextField alloc] initWithFrame:textFieldRect];
self.textFieldAge.placeholder = @"Age";
self.textFieldAge.borderStyle = UITextBorderStyleRoundedRect;
self.textFieldAge.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.textFieldAge.keyboardType = UIKeyboardTypeNumberPad;
self.textFieldAge.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
[self.view addSubview:self.textFieldAge];
self.barButtonAdd = [[UIBarButtonItem alloc] initWithTitle:@"Add" style:UIBarButtonItemStylePlain target:self action:@selector(createNewPerson:)];
[self.navigationItem setRightBarButtonItem:self.barButtonAdd animated:NO];
}
11、你可以看到,Add 按鈕現在已經超鏈接到了 Add Person 視圖控制器的 crateNewPerson:方法,所以我們需要執行這個方法並取得文本框裏的值,將它放置到一個新的 Person object。然後我們將需要保存這個對象到管對象的上下文並視圖控制器彈出返回到 Person List 視圖控制器(這時 PersonViewController 將需要顯示新的的 Person 到列表裏)。綜上所述,我們要確保已經將 AppDelegate.h 頭文件到 Add Person 視圖控制器執行文件以至於我們呢能創建一個新的 person 並使用在程序裏的委託管理對象上下文插入這個 person 到數據庫裏:12、現在讓我們執行在 Add Person 視圖控制器的 createNewPerson:方法:
- (void)createNewPerson:(id)paramSender{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:managedObjectContext];
if (newPerson != nil) {
newPerson.firstName = self.textFieldFirstName.text;
newPerson.lastName = self.textFieldLastName.text;
newPerson.age = [NSNumber numberWithInteger:[self.textFieldAge.text integerValue]];
NSError *savingError = nil;
if ([managedObjectContext save:&savingError]){
[self.navigationController popViewControllerAnimated:YES];
} else {
NSLog(@"Failed to save the managed object context.");
}
}
}
13、爲了提供更佳的用戶體驗,當視圖顯示到屏幕上時,我們可以自動在 First Name 文本框裏顯示鍵盤:
- (void)viewWillAppear:(BOOL)animated{
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidAppear:(BOOL)paramAnimated{
[super viewDidAppear:paramAnimated];
[self.textFieldFirstName becomeFirstResponder];
}
14、我們完成了Add Person視圖控制器。你可以自己先動手嘗試着完成了。現在讓我們到 Person List 視圖控制器並在初始化時實例化獲取結果控制器
#import "PersonListViewController.h"
#import "AddPersonViewController.h"
#import "AppDelegate.h"
#import "Person.h"
@interface PersonListViewController ()
@end
@implementation PersonListViewController
- (NSManagedObjectContext *) managedObjectContext{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
return managedObjectContext;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self != nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:[self managedObjectContext]];
NSSortDescriptor *ageSort = [[NSSortDescriptor alloc]initWithKey:@"age" ascending:YES];
NSSortDescriptor *firstNameSort = [[NSSortDescriptor alloc]initWithKey:@"firstName" ascending:YES];
//先排列age 如果age相同再排列firstName
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:ageSort,firstNameSort, nil];
fetchRequest.sortDescriptors = sortDescriptors;
[fetchRequest setEntity:entity];
self.personsFRC = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[self managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
self.personsFRC.delegate = self;
NSError *fetchingError = nil;
if ([self.personsFRC performFetch:&fetchingError]) {
NSLog(@"Successfully fetched");
}else{
NSLog(@"Failed to fetch");
}
}
return self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.personsFRC.sections objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *result = nil;
static NSString *PersonTableViewCell = @"PersonTableViewCell";
result = [tableView dequeueReusableCellWithIdentifier:PersonTableViewCell];
if (result == nil) {
result = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:PersonTableViewCell];
result.selectionStyle = UITableViewCellSelectionStyleNone;
}
Person *person = [self.personsFRC objectAtIndexPath:indexPath];
result.textLabel.text = [person.firstName stringByAppendingFormat:@"%@",person.lastName];
result.detailTextLabel.text = [NSString stringWithFormat:@"Age:%lu",(unsigned long)[person.age unsignedIntegerValue]];
return result;
}
16、現在你可以運行這個程序並自己來測試它。現在在程序的唯一問題就是假如用戶 在 Add Person 視圖控制器並添加一個新的 person 到管理對象上下文,當用戶返回到 Person List視圖控制器時,新插入的新person將不會在列表裏顯示。這是因爲我們獲取結 果控制器不知道新的對象在管理所對象上下文裏。解決這個問題的方法是在Person List視 圖控制器裏執行獲取結果控制器對象的 controllerDidChangeContent:委託方法。當我們創
建獲取結果控制器時,我們將視圖控制器變成獲取結果控制器的委託,然後我們執行這個 方法。當管理對象上下文的對象發生變化時將調用這個方法,一旦這個方法被調用時,我 們可以繼續並加載桌面視圖:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[self.tableViewPersons reloadData];
}
17、接下來在 Person List 視圖控制器裏要做的就是提交編輯:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
Person *personToDelete = [self.personsFRC objectAtIndexPath:indexPath];
/* Very important: we need to make sure we are not reloading the table view while deleting the managed object */
self.personsFRC.delegate = nil;
[[self managedObjectContext]deleteObject:personToDelete];
if ([personToDelete isDeleted]) {
NSError *savingError = nil;
if ([[self managedObjectContext]save:&savingError]) {
NSError *fetchingError = nil;
if ([self.personsFRC performFetch:&fetchingError]) {
NSLog(@"Successfully fetched");
NSArray *rowsToDelete = [[NSArray alloc]initWithObjects:indexPath, nil];
[_tableViewPersons deleteRowsAtIndexPaths:rowsToDelete withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
}
self.personsFRC.delegate = self;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewCellEditingStyleDelete;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated{
[super setEditing:editing animated:animated];
if (editing) {
[self.navigationItem setRightBarButtonItem:nil animated:YES];
}else{
[self.navigationItem setRightBarButtonItem:self.barButtonAddPerson animated:YES];
}
[self.tableViewPersons setEditing:editing animated:YES];
}