iPhone开发之 Storyboard app with Core Data
转载自---http://maybelost.com/2011/12/tutorial-storyboard-app-with-core-data/
首先看一下完成效果图
1.打开Xcode<本人xcode版本是4.2>
选择Empty Application模板, 命名为PictureList, 复选框选中 Core Data 和 Use Automatic Reference Counting
<可选>添加新的分组命名为Core Data, 把PictureList.xcdatamodeld拖到Core Data分组下
2.添加Entities
1>点击Add Entity添加Users对象, 再在右边Attributes里添加username和password属性, 设置类型为String
2>同上操作添加Pictures对象, 同样的方法为Pictures添加title<类型为String>, desc<类型为String>, smallPicture<类型为Binary Data>属性
3>按下Shift键, 选中刚创建的两个对象, 然后在左边Core Data分组上右键, 依次操作New File --> Core Data --> NSManagedObject subclass
完成之后效果如图
4.创建storyboard
1>右键Supporting Files group依次操作 New File-->User Interface-->Storyboard 命名为PictureListStoryboard
2>选中PictureList工程, 在右边把PictureListStoryboard设置为Main Storyboard
3>最后打开AppDelegate.m修改didFinishLaunchingWithOptions方法, 删掉除了return YES;之外的代码
5.添加默认用户到Core Data
1>打开AppDelegate.m, 导入Users.h, 然后在didFinishLaunchingWithOptions方法添加如下代码
// Get a reference to the stardard user defaults NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; // Check if the app has run before by checking a key in user defaults if ([prefs boolForKey:@"hasRunBefore"] != YES) { // Set flag so we know not to run this next time [prefs setBool:YES forKey:@"hasRunBefore"]; [prefs synchronize]; // Add our default user object in Core Data Users *user = (Users *)[NSEntityDescription insertNewObjectForEntityForName:@"Users" inManagedObjectContext:self.managedObjectContext]; [user setUsername:@"admin"]; [user setPassword:@"password"]; // Commit to core data NSError *error; if (![self.managedObjectContext save:&error]) NSLog(@"Failed to add default user with error: %@", [error domain]); }
6.添加Login View Controller
1>在PictureList右键, 依次New file-->Cocoa Touch-->UIViewController Subclass 命名为LoginViewController
修改LoginViewController.h
#import <UIKit/UIKit.h> @interface LoginViewController : UIViewController @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (strong, nonatomic) IBOutlet UITextField *usernameField; @property (strong, nonatomic) IBOutlet UITextField *passwordField; @end
修改LoginViewController.m
#import "LoginViewController.h" #import "CoreDataHelper.h" @implementation LoginViewController // Synthesize accessors @synthesize managedObjectContext, usernameField, passwordField; // When we are done editing on the keyboard - (IBAction)resignAndLogin:(id)sender { // Get a reference to the text field on which the done button was pressed UITextField *tf = (UITextField *)sender; // Check the tag. If this is the username field, then jump to the password field automatically if (tf.tag == 1) { [passwordField becomeFirstResponder]; // Otherwise we pressed done on the password field, and want to attempt login } else { // First put away the keyboard [sender resignFirstResponder]; // Set up a predicate (or search criteria) for checking the username and password NSPredicate *pred = [NSPredicate predicateWithFormat:@"(username == %@ && password == %@)", [usernameField text], [passwordField text]]; // Actually run the query in Core Data and return the count of found users with these details // Obviously if it found ANY then we got the username and password right! if ([CoreDataHelper countForEntity:@"Users" withPredicate:pred andContext:managedObjectContext] > 0) // We found a matching login user! Force the segue transition to the next view [self performSegueWithIdentifier:@"LoginSegue" sender:sender]; else // We didn't find any matching login users. Wipe the password field to re-enter [passwordField setText:@""]; } } // When the view reappears after logout we want to wipe the username and password fields - (void)viewWillAppear:(BOOL)animated { [usernameField setText:@""]; [passwordField setText:@""]; } @end
2>在AppDelegate.m导入LoginViewController.h, 再didFinishLaunchingWithOptions方法添加如下代码
// Pass the managed object context to the root view controller (the login view) LoginViewController *rootView = (LoginViewController *)self.window.rootViewController; rootView.managedObjectContext = self.managedObjectContext;
7.添加登陆界面
1>打开PictureListStoryboard.storyboard
添加一个View Controller到storyboard, 选中VC在Identity Inspector设置Class为LoginViewController
2>添加用户名和密码UILabels和Text Fields, 右键点击或者按着Ctrl左键点击Login View Controller拖动到Username输入框
,在弹出的list里选择usernameField; 然后拖动到Password输入框, 选择passwordField
3>选中Username, 在右边的Attributes Inspector中设置Tag为1, 设置Password的Tag值为2, 并且设置Secure为选中状态
4>右键Username Field, 在弹出的list里点击Did end on Exit右边的小圆点, 拖动到Login VC, 选择resignAndLogin,为
Password执行同样的操作
5>添加一个新的Table VC到storyboard作为登陆成功之后的界面, 右键点击Login VC拖动到刚添加的界面, 选中弹出列表里的Modal, 此时两个
界面已经创建关联, 选中刚才创建的VC, 设置Attributes Inspector<快捷键alt+cmd+4>里的Identifier为LoginSegue
8.添加Navigation Controller 和 logout
选中Table VC, 依次Editor menu-->Embed In-->Navigator Controller, 拖动Bar Button Item到Table View navigation bar
的左边, 然后双击修改名称为Logout, 再次添加Bar Button Item到右边, 然后修改Identifier为Add
9.创建PictureList Table
1>右键PictureList分组, 依次New File-->Cocoa Touch-->UIViewController subclass-->设置父类为UITableViewController,
命名:PictureListMainTable
2>修改代码
PictureListMainTable.h
#import <UIKit/UIKit.h> @interface PictureListMainTable : UITableViewController @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (strong, nonatomic) NSMutableArray *pictureListData; - (void)readDataForTable; @end
PictureListMainTable.m
#import "PictureListMainTable.h" #import "CoreDataHelper.h" #import "Pictures.h" @implementation PictureListMainTable @synthesize managedObjectContext, pictureListData; // When the view reappears, read new data for table - (void)viewWillAppear:(BOOL)animated { // Repopulate the array with new table data [self readDataForTable]; } // Grab data for table - this will be used whenever the list appears or reappears after an add/edit - (void)readDataForTable { // Grab the data pictureListData = [CoreDataHelper getObjectsForEntity:@"Pictures" withSortKey:@"title" andSortAscending:YES andContext:managedObjectContext]; // Force table refresh [self.tableView reloadData]; } #pragma mark - Actions // Button to log out of app (dismiss the modal view!) - (IBAction)logoutButtonPressed:(id)sender { [self dismissModalViewControllerAnimated:YES]; } #pragma mark - Table view data source // Return the number of sections in the table - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } // Return the number of rows in the section (the amount of items in our array) - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [pictureListData count]; } // Create / reuse a table cell and configure it for display - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // Get the core data object we need to use to populate this table cell Pictures *currentCell = [pictureListData objectAtIndex:indexPath.row]; // Fill in the cell contents cell.textLabel.text = [currentCell title]; cell.detailTextLabel.text = [currentCell desc]; // If a picture exists then use it if ([currentCell smallPicture]) { cell.imageView.contentMode = UIViewContentModeScaleAspectFit; cell.imageView.image = [UIImage imageWithData:[currentCell smallPicture]]; } return cell; } // Swipe to delete has been used. Remove the table item - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // Get a reference to the table item in our data array Pictures *itemToDelete = [self.pictureListData objectAtIndex:indexPath.row]; // Delete the item in Core Data [self.managedObjectContext deleteObject:itemToDelete]; // Remove the item from our array [pictureListData removeObjectAtIndex:indexPath.row]; // Commit the deletion in core data NSError *error; if (![self.managedObjectContext save:&error]) NSLog(@"Failed to delete picture item with error: %@", [error domain]); // Delete the row from the data source [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } @end
3>选中Table VC, 在Identity Inspector里设置关联Class为PictureListMainTable, 然后在Attributes Inspector里
设置Table View, 首先style舍为Grouped, Separator设为Single Line, 然后选中prototype cell设置style为Subtitle
在size inspector里设置Row Height的值为84
4>右键选中Logout按钮, 拖动到Picture List Main Table图标, 选择logoutButtonPressed
5>打开LoginViewController.m添加如下代码, 记得导入PictureListMainTable.h文件
// When we have logged in successfully, we need to pass the managed object context to our table view (via the navigation controller) // so we get a reference to the navigation controller first, then get the last controller in the nav stack, and pass the MOC to it - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { UINavigationController *navController = (UINavigationController *)[segue destinationViewController]; PictureListMainTable *piclist = (PictureListMainTable *)[[navController viewControllers] lastObject]; piclist.managedObjectContext = managedObjectContext; }
10.添加详细画面
1>右键PictureList分组, 依次New File-->Cocoa Touch-->UIViewController subclass创建
UITableViewController的子类, 命名为PictureListDetail
2>修改代码
PictureListDetail.h
#import <UIKit/UIKit.h> #import "Pictures.h" @interface PictureListDetail : UITableViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate> @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (strong, nonatomic) Pictures *currentPicture; @property (strong, nonatomic) IBOutlet UITextField *titleField; @property (strong, nonatomic) IBOutlet UITextField *descriptionField; @property (strong, nonatomic) IBOutlet UIImageView *imageField; @property (strong, nonatomic) UIImagePickerController *imagePicker; @end
PictureListDetail.h
#import "PictureListDetail.h" @implementation PictureListDetail @synthesize managedObjectContext; @synthesize currentPicture; @synthesize titleField, descriptionField, imageField; @synthesize imagePicker; #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; // If we are editing an existing picture, then put the details from Core Data into the text fields for displaying if (currentPicture) { [titleField setText:[currentPicture title]]; [descriptionField setText:[currentPicture desc]]; if ([currentPicture smallPicture]) [imageField setImage:[UIImage imageWithData:[currentPicture smallPicture]]]; } } #pragma mark - Button actions - (IBAction)editSaveButtonPressed:(id)sender { // If we are adding a new picture (because we didnt pass one from the table) then create an entry if (!currentPicture) self.currentPicture = (Pictures *)[NSEntityDescription insertNewObjectForEntityForName:@"Pictures" inManagedObjectContext:self.manag edObjectContext]; // For both new and existing pictures, fill in the details from the form [self.currentPicture setTitle:[titleField text]]; [self.currentPicture setDesc:[descriptionField text]]; if (imageField.image) { // Resize and save a smaller version for the table float resize = 74.0; float actualWidth = imageField.image.size.width; float actualHeight = imageField.image.size.height; float divBy, newWidth, newHeight; if (actualWidth > actualHeight) { divBy = (actualWidth / resize); newWidth = resize; newHeight = (actualHeight / divBy); } else { divBy = (actualHeight / resize); newWidth = (actualWidth / divBy); newHeight = resize; } CGRect rect = CGRectMake(0.0, 0.0, newWidth, newHeight); UIGraphicsBeginImageContext(rect.size); [imageField.image drawInRect:rect]; UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // Save the small image version NSData *smallImageData = UIImageJPEGRepresentation(smallImage, 1.0); [self.currentPicture setSmallPicture:smallImageData]; } // Commit item to core data NSError *error; if (![self.managedObjectContext save:&error]) NSLog(@"Failed to add new picture with error: %@", [error domain]); // Automatically pop to previous view now we're done adding [self.navigationController popViewControllerAnimated:YES]; } // Pick an image from album - (IBAction)imageFromAlbum:(id)sender { imagePicker = [[UIImagePickerController alloc] init]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; [self presentViewController:imagePicker animated:YES completion:nil]; } // Take an image with camera - (IBAction)imageFromCamera:(id)sender { imagePicker = [[UIImagePickerController alloc] init]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceRear; [self presentViewController:imagePicker animated:YES completion:nil]; } // Resign the keyboard after Done is pressed when editing text fields - (IBAction)resignKeyboard:(id)sender { [sender resignFirstResponder]; } #pragma mark - Image Picker Delegate Methods // Dismiss the image picker on selection and use the resulting image in our ImageView - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo { [imagePicker dismissModalViewControllerAnimated:YES]; [imageField setImage:image]; } // On cancel, only dismiss the picker controller - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [imagePicker dismissModalViewControllerAnimated:YES]; } @end
3>修改PictureListMainTable.m
// When add is pressed or a table row is selected - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get a reference to our detail view PictureListDetail *pld = (PictureListDetail *)[segue destinationViewController]; // Pass the managed object context to the destination view controller pld.managedObjectContext = managedObjectContext; // If we are editing a picture we need to pass some stuff, so check the segue title first if ([[segue identifier] isEqualToString:@"EditPicture"]) { // Get the row we selected to view NSInteger selectedIndex = [[self.tableView indexPathForSelectedRow] row]; // Pass the picture object from the table that we want to view pld.currentPicture = [pictureListData objectAtIndex:selectedIndex]; } }
11.修改详细信息页面
1>添加一个新的Table VC, 然后在Identity Inspector中设置关联类为PictureListDetail, 然后在
attributes inspector中设置content为Static Cells, sections值为2, style为Grouped
2>右键选中"Add"按钮,拖动到Picture List Detail, 选择Push, 然后选中Segue, 修改identifier
为AddPicture,接着在Picture List Main Table中选中单个Cell,右键拖动到Picture List Detail
选择Push,然后修改Segue值为EditPicture
3>选中Picture List Detail里的Cell,删除Cell的名字,修改高度为95,添加一个Image View,设置为80x80px
添加两个按钮,命名为"Pick from album","Take a photo"; 第二个Cell,删除一行,然后给第一行添加标签
Title,添加Text Field,在Attributes Inspector中设置,第二行添加标签Description和TextField
4>拖动Bar Button Item添加到Table VC的右上角,命名为"Save",在Attributes Inspector中设置Identifier
为Save;右键选中Save按钮,拖动至Picture List Detail,选择editSaveButtonPressed;
5>右键选中 Picture List Detail,拖动至Image View,选中imageField; 同样给titleField和descriptionField
进行设置,右键Text Field,拖动Did end on exit后的circle到Picture List Detail,选择resignKeyboard
6>右键"Pick from Album"按钮到Picture List Detail,选择imageFromAlbum;右键"Take a photo"按钮,
选择imageFromCamera