Pull to refresh

Разбираем iPhone Core Data Recipes. Часть 2

Reading time10 min
Views8.5K

Introduction


Данная статья, вторая и заключительная статья из серии «Разбираем iPhone Core Data Recipes». Первую часть статьи, вы можете прочитать тут. Цель серии статей — помочь начинающему iOS разработчику, понять, как правильно работать с SQLite базой данных используя Core Data на примере iPhone Core Data Recipes. В заключительной статье мы рассмотрим функционал добавления, редактирования и удаления записей из базы данных.

Prerequisites


Для самостоятельного изучения исходных текстов данного приложения, вам необходим стандартный набор инструментов:
  • Mac OS X
  • Xcode

Данный набор позволит вам просмотреть, изменить и запустить приложение на симуляторе. В случае же, если вы захотите попробовать запустить его на настоящем iPhone, требуется участие в iOS Developer Program.

А также, что немало важно, нужно базовое понимание структуры языка Objective-C и приложения.

Ссылки на используемые материалы и инструменты предоставлены в разделе References.

How To Create a New Recipe


Итак, первое что нам необходимо — это создать новый рецепт. За создание нового рецепта, в данном проекте, отвечает view controller — RecipeAddViewController. Рассмотрим его содержимое.

RecipeAddViewController.h
@protocol RecipeAddDelegate;
@class Recipe;

@interface RecipeAddViewController : UIViewController <UITextFieldDelegate> {
    @private
        //Объект Recipe, уже привязанный managedObjectContext
        Recipe *recipe;
        //Поле для ввода названия рецепта
        UITextField *nameTextField;
        id <RecipeAddDelegate> delegate;
}

@property(nonatomic, retain) Recipe *recipe;
@property(nonatomic, retain) IBOutlet UITextField *nameTextField;
@property(nonatomic, assign) id <RecipeAddDelegate> delegate;

//Сохраняем новый рецепт
- (void)save;
//Отменяем создание нового рецепта
- (void)cancel;

@end

@protocol RecipeAddDelegate <NSObject>
// recipe == nil on cancel
- (void)recipeAddViewController:(RecipeAddViewController *)recipeAddViewController didAddRecipe:(Recipe *)recipe;
@end

RecipeAddViewController.m
#import "RecipeAddViewController.h"
#import "Recipe.h"

@implementation RecipeAddViewController

@synthesize recipe;
@synthesize nameTextField;
@synthesize delegate;


- (void)viewDidLoad {
    
    // Конфигурируем navigation bar
    self.navigationItem.title = @"Add Recipe";
    
    //Создаем кнопку Cancel и привязываем ее к действию  cancel
    UIBarButtonItem *cancelButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStyleBordered target:self action:@selector(cancel)];
    self.navigationItem.leftBarButtonItem = cancelButtonItem;
    [cancelButtonItem release];
    
    //Создаем кнопку Save и привязываем ее к действию save
    UIBarButtonItem *saveButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Save" style:UIBarButtonItemStyleDone target:self action:@selector(save)];
    self.navigationItem.rightBarButtonItem = saveButtonItem;
    [saveButtonItem release];
	
	[nameTextField becomeFirstResponder];
}
...
//Сохраняем новый рецепт
- (void)save {
    
    recipe.name = nameTextField.text;

	NSError *error = nil;
	if (![recipe.managedObjectContext save:&error]) {
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}		
    
	[self.delegate recipeAddViewController:self didAddRecipe:recipe];
}

//Отменяем создание нового рецепта
- (void)cancel {
	
	[recipe.managedObjectContext deleteObject:recipe];

	NSError *error = nil;
	if (![recipe.managedObjectContext save:&error]) {
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}		

    [self.delegate recipeAddViewController:self didAddRecipe:nil];
}

//Т.к. проект не использует ARC, необходимо подчистить память
- (void)dealloc {
    [recipe release];    
    [nameTextField release];    
    [super dealloc];
}

@end

В итоге, форма добавления нового рецепта, выглядит следующим образом

Детальное рассмотрение действий save & cancel будет ниже.

Открываем форму создания нового рецепта


Кнопка для создания нового рецепта расположена в главном окне нашего приложения (см. скриншот выше). Рассмотрим её более делатьно.

Данная кнопка создается в методе viewDidLoad контроллера RecipeListTableViewController.
UIBarButtonItem *addButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add:)];
    self.navigationItem.rightBarButtonItem = addButtonItem;
    [addButtonItem release];

К данной кнопке привязано действие add, которое также находится в контроллере RecipeListTableViewController (см. комментарии в исходном коде).
- (void)add:(id)sender {
   //Создаем контроллер RecipeAddViewController который мы рассматривали выше
    RecipeAddViewController *addController = [[RecipeAddViewController alloc] initWithNibName:@"RecipeAddView" bundle:nil];

    //Связываем его с RecipeListTableViewController
    addController.delegate = self;

	//В managedObjectContext создаем объект Recipe с помощью NSEntityDescription, другими словами - создаем новую запись в таблице Recipe
	Recipe *newRecipe = [NSEntityDescription insertNewObjectForEntityForName:@"Recipe" inManagedObjectContext:self.managedObjectContext];

        //Передаем новый объект Recipe контроллеру RecipeAddViewController
	addController.recipe = newRecipe;

    //Модально оторбажаем контроллер RecipeAddViewController
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:addController];
    [self presentModalViewController:navigationController animated:YES];
    
    [navigationController release];
    [addController release];
}

После выполнения данного действия, откроется форма добавления нового рецепта рассмотренная выше. На этой форме, для пользователя доступно два действия — Save & Cancel.

Сохраняем новый рецепт

- (void)save {
    //Свойству name объекта Recipe устанавливаем значение из поля
    recipe.name = nameTextField.text;

	NSError *error = nil;
        //Сохраняем новую запись
	if (![recipe.managedObjectContext save:&error]) {
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}		
        //Отображаем созданный рецепт с помощью RecipeDetailViewController (исходный текст для этого действия см. ниже в секции "Отображаем созданный рецепт")
	[self.delegate recipeAddViewController:self didAddRecipe:recipe];
}

Отменяем сохранение нового рецепта

- (void)cancel {
        //Удаляем ранее созданный объект Recipe, другими словами, удаляем ранее созданную запись в базе данных
	[recipe.managedObjectContext deleteObject:recipe];

	NSError *error = nil;
	if (![recipe.managedObjectContext save:&error]) {
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}		
    //Отображаем список рецептов
    [self.delegate recipeAddViewController:self didAddRecipe:nil];
}

Отображаем созданный рецепт

- (void)recipeAddViewController:(RecipeAddViewController *)recipeAddViewController didAddRecipe:(Recipe *)recipe {
    if (recipe) {        
        // Если объект Recipe не равняется nil - тогда отображаем его
        [self showRecipe:recipe animated:NO];
    }
    [self dismissModalViewControllerAnimated:YES];
}
- (void)showRecipe:(Recipe *)recipe animated:(BOOL)animated {
    // Создаем контроллер который отображает детальную информацию о рецепте, передаем в него рецепт и отображаем
    RecipeDetailViewController *detailViewController = [[RecipeDetailViewController alloc] initWithStyle:UITableViewStyleGrouped];
    detailViewController.recipe = recipe;
    
    [self.navigationController pushViewController:detailViewController animated:animated];
    [detailViewController release];
}

How To Create a New Ingredient



Как вы можете видеть на скриншоте выше, при просмотре детальной информации о рецепте, в режиме редактирования, доступна ссылка «Add Ingredient». Создается она в методе

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

контроллера RecipeDetailViewController.

static NSString *AddIngredientCellIdentifier = @"AddIngredientCell";
			
			cell = [tableView dequeueReusableCellWithIdentifier:AddIngredientCellIdentifier];
			if (cell == nil) {
				 // Create a cell to display "Add Ingredient".
				cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:AddIngredientCellIdentifier] autorelease];
				cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
			}
            cell.textLabel.text = @"Add Ingredient";

За добавление и редактирование ингредиентов отвечает контроллер IngredientDetailViewController.

IngredientDetailViewController.h
@class Recipe, Ingredient, EditingTableViewCell;

@interface IngredientDetailViewController : UITableViewController {
    @private
        Recipe *recipe;
        Ingredient *ingredient;
        
        EditingTableViewCell *editingTableViewCell;
}

@property (nonatomic, retain) Recipe *recipe;
@property (nonatomic, retain) Ingredient *ingredient;

@property (nonatomic, assign) IBOutlet EditingTableViewCell *editingTableViewCell;

@end

IngredientDetailViewController.m
...
- (id)initWithStyle:(UITableViewStyle)style {
    if (self = [super initWithStyle:style]) {
        UINavigationItem *navigationItem = self.navigationItem;
        navigationItem.title = @"Ingredient";

        UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)];
        self.navigationItem.leftBarButtonItem = cancelButton;
        [cancelButton release];

        UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(save:)];
        self.navigationItem.rightBarButtonItem = saveButton;
        [saveButton release];
    }
    return self;
}
...
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    //Отображаем только две строки, название и количество
    return 2;
}

//Создаем поля для отображения и редактирования
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *IngredientsCellIdentifier = @"IngredientsCell";
    
    EditingTableViewCell *cell = (EditingTableViewCell *)[tableView dequeueReusableCellWithIdentifier:IngredientsCellIdentifier];
    if (cell == nil) {
		[[NSBundle mainBundle] loadNibNamed:@"EditingTableViewCell" owner:self options:nil];
        cell = editingTableViewCell;
		self.editingTableViewCell = nil;
    }
    
    if (indexPath.row == 0) {
        cell.label.text = @"Ingredient";
        cell.textField.text = ingredient.name;
        cell.textField.placeholder = @"Name";
    }
	else if (indexPath.row == 1) {
        cell.label.text = @"Amount";
        cell.textField.text = ingredient.amount;
        cell.textField.placeholder = @"Amount";
    }

    return cell;
}
...

- (void)save:(id)sender {
	
	NSManagedObjectContext *context = [recipe managedObjectContext];
	
	/*
        Если объект ингредиента не создан - создаем и конфигурируем его
	 */
    if (!ingredient) {
        self.ingredient = [NSEntityDescription insertNewObjectForEntityForName:@"Ingredient" inManagedObjectContext:context];
        [recipe addIngredientsObject:ingredient];
		ingredient.displayOrder = [NSNumber numberWithInteger:[recipe.ingredients count]];
    }
	
	/*
          Обновляем объект ингредиента значениями из текстовых полей
	 */
    EditingTableViewCell *cell;
	
    cell = (EditingTableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    ingredient.name = cell.textField.text;
	
    cell = (EditingTableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
    ingredient.amount = cell.textField.text;
	
	/*
	 Сохраняем изменения
	 */
	NSError *error = nil;
	if (![context save:&error]) {
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}
	
    [self.navigationController popViewControllerAnimated:YES];
}
...
@end

Форма добавления ингредиента выглядит следующим образом

При нажатии на кнопку Save ингредиент будет сохранен в базе данных и отображен в списке. При нажатии на кнопку Cancel новый ингредиент сохранен не будет.

How To Remove an Existing Recipe


Контроллер RecipeListTableViewController содержит следующий код для поддержки удаления рецепта
- (void)viewDidLoad {
...
    //Создаем кнопку Edit
    self.navigationItem.leftBarButtonItem = self.editButtonItem;		
...
}
...
// Включаем поддержку редактирования в UITableView
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Удалем выделенный рецепт
		NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
		[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
		
		// Сохраняем изменения
		NSError *error;
		if (![context save:&error]) {
			NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
			abort();
		}
	}   
}

Теперь, при нажатии на кнопку Edit или выполнение жеста над UITableViewCell, UITableView перейдет в режим редактирования, что позволит вам удалить рецепт (см. скриншоты ниже).

При удалении рецепта, все связанные объекты (ингредиенты и картинка) в базе данных будут также удалены.

How To Remove an Existing Ingredient


В контроллере RecipeDetailViewController, все устроено аналогичным образом, за исключением добавления картинки (рассмотрено не будет).
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
	UITableViewCellEditingStyle style = UITableViewCellEditingStyleNone;
    // Only allow editing in the ingredients section.
    // В секции отображения ингредиентов, последняя строка добавляется автоматически (см. tableView:cellForRowAtIndexPath:), она требуется для добавления нового ингредиента, таким образом в списке ингредиентов включаем стиль удаления, а в последнем элементе стиль добавления
    if (indexPath.section == INGREDIENTS_SECTION) {
        if (indexPath.row == [recipe.ingredients count]) {
            style = UITableViewCellEditingStyleInsert;
        }
        else {
            style = UITableViewCellEditingStyleDelete;
        }
    }
    
    return style;
}


- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    
    // Только удаление и только в списке ингредиентов
    if ((editingStyle == UITableViewCellEditingStyleDelete) && (indexPath.section == INGREDIENTS_SECTION)) {
        // Удаляем ингредиент
        Ingredient *ingredient = [ingredients objectAtIndex:indexPath.row];
        [recipe removeIngredientsObject:ingredient];
        [ingredients removeObject:ingredient];
        
        NSManagedObjectContext *context = ingredient.managedObjectContext;
        [context deleteObject:ingredient];
        
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
    }
}

Форма добавления и удаления ингредиентов выглядит следующим образом


Conclusion


В данной статье, я специально не рассматривал создание картинки для рецепта — предполагая самостоятельное изучение читателем, данной функциональности (см. метод photoTapped контроллера RecipeDetailViewController). Приведенные примеры исходного кода, являются выдержками, изучить исходный код полностью, можно скачав проект (см. раздел References). Я надеюсь, что проделанная мной работа, по написанию этой серии статей, оказалась полезна для тебя — дорогой читатель! Всем спасибо за внимание и за проявленое терпение.

References


Tags:
Hubs:
+5
Comments2

Articles

Change theme settings