Интересные статьи об Apple, приложениях для iPhone и iPad, iTunes

Core Data для iOS. Глава №3. Теоретическая часть

Core Data для iOS. Глава №3. Теоретическая часть

Хабралюди, добрый день!

Сегодня хочу начать написание ряда лекций с практическими заданиями по книге Михаеля Привата и Роберта Варнера «Pro Core Data for iOS», которую можете купить по этой ссылке. Каждая глава будет содержать теоретическую и практическую часть.

Содержание:

  • Глава №1. Приступаем (Практическая часть)
  • Глава №2. Усваиваем Core Data (Практическая часть)
  • Глава №3. Хранение данных: SQLite и другие варианты
  • Глава №4. Создание модели данных
  • Глава №5. Работаем с объектами данных
  • Глава №6. Обработка результатирующих множеств
  • Глава №7. Настройка производительности и используемой памяти
  • Глава №8. Управление версиями и миграции
  • Глава №9. Управление таблицами с использованием NSFetchedResultsController
  • Глава №10. Использование Core Data в продвинутых приложениях
  • Вступление

    В этой главе мы создадим приложение с двумя таблицами и «один-ко-многим» отношением между ними. Представьте, что вы вызвались добровольцем следить за командами из младшей футбольной лиги и их составом. В нашей модели данных будет две таблицы: Team (хранит информацию о наименовании команды и цвете их формы) и Player (хранит информацию об игроке команды — почтовый ящик, имя и фамилию). Понятно, что у одной команды множество игроков, а игрок принадлежит одной команде.
    Создадим мы это приложение сперва с использованием SQLite в качестве хранилища, а потом попробуем использовать другие варианты (хранение в памяти и произвольные хранилища (atomic store)).

    Интерфейс


    Наше приложение назовём League Manager и состоять оно будет из 4 экранов:

    • Список команд
    • Добавить/редактировать команду
    • Список игроков
    • Добавить /редактировать игрока



    Экран со списком команд отображает сохраненные локально футбольные команды с цветами их формы. На экране присутствует кнопка + для добавления новой команды и кнопка Edit для удаления. Вот как выглядит экран:

    img



    Экран добавления/редактирования футбольной команды состоит из двух полей для ввода наименования команды и цвета их формы. Данный экран отображается после нажатия на кнопку + на экране «Список команд» для добавления новой команды, либо при нажатии на одну из команд из списка для её редактирования.

    img



    Экран со списком игроков отображает всех игроков определенной команды. Переход на данный экран осуществляется после нажатия на синюю стрелочку на экране «Список команд» напротив каждой команды:

    img



    Как и с командами, при нажатии на + появляется экран добавления нового игрока в команду, а при нажатии на Edit можно редактировать информацию об игроке.

    img



    Теперь, когда мы знаем что нам предстоит делать, можем приступать.

    Использование SQLite в качестве хранилища


    Запускаем ХКод и создаем новый проект.

    img



    Называем его LeagueManager и в качестве идентификатора компании пишем book.coredata.

    img



    После создания проекта:

    img



    На данном этапе создания приложения, самым важным компонентом и частью является создание модели данных. Откроем *.xcdatamodeld в ХКоде:

    img



    Удалите (нажатие клавиши Delete) уже имеющуюся сущность Event, которая нам не пригодится.

    Начнем с создания сущности Team:

    img



    Создадим два атрибута у сущности Team:

    • name (String)
    • uniformColor (String)


    Получим следующую картину:

    img



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

    img



    Далее создаем сущность Player с тремя атрибутами:

    • firstName (String)
    • lastName (String)
    • email (String)
    img

    Связь «один-ко-многим»


    Для создания новой связи между сущностями Team и Player необходимо сперва выбрать сущность Team и в разделе Relationships нажать + и назвать новую связь players, для Destination выбрать сущность Player. Справа установите флаг «To-Many Relationship» при выбранной связи players. В качестве правила удаления выберите Cascade (при удалении команды будут автоматически удалены все игроки команды).

    img



    Теперь необходимо создать связь со стороны игрока (сущности Player). Добавляем сущности Player связь под названием team, в поле Destination выбираем Team, а в поле Invers выбираем players (после этого не забываем установить поле Inverse для связи players у сущности Team)

    img

    Построение пользовательского интерфейса


    После завершения создания модели данных необходимо заняться написанием кода для отображения этих самых данных. ХКод автоматически генерирует весь необходимый код для отображения списка команд; следующим заданием будет немного подкорректировать его для работы с сущностями Team, а не Event.

    Нам необходим метод для добавления команды в хранилище, давайте добавим его в файл MasterViewController.m, предварительно не забыв описать в MasterViewController.h.
    Код в MasterViewController.h выглядит теперь следующим образом:
    #import <UIKit/UIKit.h>
    #import <CoreData/CoreData.h>
    @interface MasterViewController : UITableViewController <NSFetchedResultsControllerDelegate>
    @property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
    @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
    - (void)insertTeamWithName:(NSString *)name uniformColor:(NSString *)uniformColor;
    - (void)saveContext;
    @end
    

    Откройте MasterViewController.m и подкорректируйте наименование экрана следующим образом:
    self.title = NSLocalizedString(@"League Manager", @"League Manager");
    

    Теперь реализуем метод добавления новой команды:
    - (void)insertTeamWithName:(NSString *)name uniformColor:(NSString *)uniformColor
    {
       NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
       NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
       NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
       [newManagedObject setValue:name forKey:@"name"];
       [newManagedObject setValue:uniformColor forKey:@"uniformColor"];
       [self saveContext];
    }
    

    Теперь найдем строку примерно следующего содержания:
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext];
    

    и заменим на:
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Team" inManagedObjectContext:self.managedObjectContext];
    

    Далее необходимо внести еще одно маленькое изменение в существующий код, заменить эту строку:
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] iniWithKey:@"timestamp" ascending:NO];
    

    на:
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] iniWithKey:@"name" ascending:NO];
    

    После создания нового объекта мы устанавливаем его свойствам значения, которые были переданы:
       [newManagedObject setValue:name forKey:@"name"];
       [newManagedObject setValue:uniformColor forKey:@"uniformColor"];
    

    После того, как свойства объекта были изменены нам необходимо его сохранить, поэтому и вызывается метод saveContext.
    - (void)saveContext
    {
       NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
       NSError *error = nil;
       if(![context save:&error]){
          NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
          abort();
       }
    }
    

    Подкорректируем еще один метод следующим образом:
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (editingStyle == UITableViewCellEditingStyleDelete) {
            [self saveContext];
        }
    }
    

    Настройка таблицы


    Ячейки таблицы всё еще сконфигурированы для отображения сущностей Event, вместо требуемых Team сущностей. Нам необходимо отобразить в одной ячейке таблицы две составляющие: имя команды и цвет формы в которой играет эта команда. Для того, чтобы достичь этого необходимо сперва изменить стиль отображаемой ячейки таблицы, как и идентификатор CellIdentifier, используемый в методе cellForRowAtIndexPath:.
    Меняем эту строку:
    static NSString* CellIdentifier = @"Cell";
    

    на:
    static NSString* CellIdentifier = @"TeamCell";
    

    И меняем тип создаваемых ячеек с таких:
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    

    на:
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
    

    В сгенерированном коде присутствует метод configureCell:atIndexPath:, в котором собственно происходит настройка ячейки для отображение. Но в текущем виде, метод работает с сущностью Event, а не Team, поэтому необходимо внести кое-какие коррективы.
    - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
       NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
       cell.textLabel.text = [[managedObject valueForKey:@"name"] description];
       cell.detailTextLabel.text = [[managedObject valueForKey:@"uniformColor"] description];
       cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
    }
    

    Создание команды


    Пока наше приложение мало что делает. Оно не может создать команду, не может создать игрока. Сейчас подходящий момент запустить приложение на выполнение, чтобы убедиться, что мы на правильном пути и всё работает. Если по каким-то причинам у вас появляются ошибки при сборке приложения, то попробуйте пройти все шаги заново, перепроверьте модель данных.

    При создании приложения из шаблона Master-Details View Application создается еще один контроллер с названием DetailViewController. Вы можете использовать этот класс, но так как нам необходимо отображать информацию о команде и игроках, то лучше избавиться от этого контроллера и создавать новые контроллеры с соответствующими именами.
    Удалите строку #import DetailViewController.h из MasterViewController.m. Найдите метод tableView:didSelectRowAtIndexPath: и очистите его тело. Выглядит следующим образом:
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    }
    


    После чего мы можем спокойно удалять все три файла DetailViewController (.h, .m, .xib). Если мы запустим приложение и нажмем на кнопку +, то приложение аварийно прекращает свою работу. Этот + по прежнему привязан к методу insertNewObject:, который мы удалили. Нам необходимо привязать к этой кнопке возможность создания новой команды, а точнее — показать модальное окно с полями для ввода информации (наименование команды и цвет формы) о новой команде. Это же окно будет использоваться для редактирования уже имеющихся команд при нажатии на ячейку с командой на экране списка команд.
    Создаем новый класс, родителем нашего класса будет являться UIViewController:

    img


    Назовем этот класс TeamViewController и не забываем установить галочку на опции With XIB for user interface.

    img


    Открываем TeamViewController.h. В League Manager, класс MasterViewController управляет NSManagedObjectContext, значит в TeamViewController будет необходима ссылка на эту среду управления объектами и соответствующий метод инициализации. Так как данный контроллер будет отвечать и за редактирование информации о команде, то при инициализации стоит передавать объект команды (для этого нам так же нужно будет создать свойство и добавить в метод инициализации). Пользовательский интерфейс экрана добавления/редактирования команды будет содержать два текстовых поля — для наименования команды и цвета их формы, для них необходимо создать соответствующие свойства в TeamViewController. На данном экране так же будут находиться две кнопки — кнопка сохранения (Save) новой команды и кнопка отмены (Cancel). В TeamViewController должны быть методы-обработчики нажатий на эти кнопки.

    TeamViewController.h
    #import <UIKit/UIKit.h>
    @class MasterViewController;
    @interface TeamViewController : UIViewController {
       IBOutlet UITextField *name;
       IBOutlet UITextField *uniformColor;
       NSManagedObject *team;
       MasterVIewController *masterController;
    }
    @property (nonatomic, retain) UITextField *name;
    @property (nonatomic, retain) UITextField *uniformColor;
    @property (nonatomic, retain) NSManagedObject *team;
    @property (nonatomic, retain) MasterViewController *masterController;
    - (IBAction)save:(id)sender;
    - (IBAction)cancel:(id)sender;
    - (id)initWithMasterController:(MasterViewController *)aMasterController team:(NSManagedObject *)aTeam;
    @end
    

    Открываем TeamViewController.m, импортируем MasterViewController.h, удаляем метод initWithNibName:, добавляем @synthesize для name, team и masterController. Добавляем вот такой метод инициализации:
    - (id)initWithMasterController:(MasterController *)aMasterController team:(NSManagedObject *)aTeam {
       if((self = [super init])){
          self.masterController = aMasterController;
          self.team = aTeam;
       }
       return self;
    }
    

    В случае, если пользователь приложения захочет создать новую команду, то параметр aTeam будет равен nil, и контроллер TeamViewController.m будет отвечать за создание нового NSManagedObject объекта. В случае же, если пользователь выберет одну из существующих команд для редактирования, то за контроллером остаётся ответственность заполнить текстовые поля на экране соответствующими данными из командного объекта (имя команды и цвет формы). Последний функционал мы добавим в метод viewDidLoad:
    - (void)viewDidLoad{
       [super viewDidLoad];
       if(team != nil){
          name.text = [team valueForKey:@"name"];
          uniformColor.text = [team valueForKey:@"uniformColor"];
       }
    }
    

    Теперь осталось реализовать обработчики событий при нажатии на кнопки Save и Cancel:
    - (IBAction)save:(id)sender
    {
       if(masterController != nil){
          if(team != nil){
             [team setValue:name.text forKey:@"name"];
             [team setValue:uniformColor.text forKey:@"uniformColor"];
             [masterController saveContext];
          } else {
             [masterController insertNewTeamWithName:name.text uniformColor:uniformColor.text];
          }
       }
       [self dismissModalViewControllerAnimated:YES];
    }
    

    Метод cancel: просто убирает окно редактирования/добавления команды.
    TeamViewController.m
    #import "TeamViewController.h"
    #import "MasterViewController.h"
    @implementation TeamViewController
    @synthesize name;
    @synthesize uniformColor;
    @synthesize team;
    @synthesize masterController;
    - (id)initWithMasterController:(MasterController *)aMasterController team:(NSManagedObejct *)aTeam {
       if((self = [super init])){
          self.masterController = aMasterController;
          self.team = aTeam;
       }
       return self;
    }
    - (void)didReceiveMemoryWarning{
       [super didReceiveMemoryWarning];
    }
    #pragma mark - View lifecycle
    - (void)viewDidLoad{
       [super viewDidLoad];
       if(team != nil){
          name.text = [team valueForKey:@"name"];
          uniformColor.text = [team valueForKey:@"uniformColor"];
       }
    }
    - (void)viewDidUnload{
       [super viewDidUnload];
    }
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
       return (interfaceOrientation == UIInterfaceOrientationPortrait);
    }
    #pragma mark - Button handlers
    - (IBAction)save:(id)sender{
       if(masterController != nil){
          if(team != nil){
             [team setValue:name.text forKey:@"name"];
             [team setValue:uniformColor.text forKey:@"uniformColor"];
             [masterController saveContext];
          } else {
             [masterController insertTeamWithName:name.text uniformColor:uniformColor.text];
          }
       }
       [self dismissModalViewControllerAnimated:YES];
    }
    - (IBAction)cancel:(id)sender{
       [self dismissModalViewControllerAnimated:YES];
    }
    @end
    


    После того, как написан код для взаимодействия с элементами пользовательского интерфейса, мы можем приступить собственно к созданию этого самого пользовательского интерфейса. Открываем TeamViewController.xib, он у нас изначально пустой должен быть. Устанавливаем туда две надписи, два поля ввода текста и две кнопк, связываем действия кнопок с соответствующими методами-обработчиками. Итоговый вид примерно такой:

    img


    Перед тем, как запускать приложение на выполнение, вернемся в MasterViewController и добавим код для отображения экрана с информацией о команде. Отображать экран редактирования команды мы должны в двух случаях: 1) пользователь нажал на + 2) пользователь нажал на команду из списка. Начнем с нажатия на кнопку +. Объявите новый метод в MasterViewController.h:
    - (void)showTeamView;
    

    Идем в MasterViewController.m, импортируем TeamViewController.h и реализуем указанный выше метод следующим образом:
    - (void)showTeamView{
       TeamViewController *team = [[TeamViewController alloc] initWithMasterController:self team:nil];
       [self presentModalViewController:teamViewController animated:YES];
    }
    

    Теперь осталось «связать» нажатие на + с действием. Переходим в viewDidLoad метод и заменяем вот этот код:
    UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject)];
    

    на этот:
    UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(showTeamView)];
    

    На данном этапе приложение уже может создавать команды, но перед тем, как запустить приложение на выполнение предлагаю добавить сразу код для редактирования команд:
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath)indexPath{
       NSManagedObject *team = [[self fetchedResultsController] objectAtIndexPath:indexPath];
       TeamViewController *team = [[TeamViewController alloc] initWithMasterController:self team:team];
       [self presentModalViewController:teamViewController animated:YES];
    }
    


    Запустите приложение на выполнение:

    img



    Теперь мы можем добавлять команды, редактировать их. Добавьте несколько команд, в списке они будут отсортированы в лексикографическом порядке.

    img
    img



    Можно заметить, что мы можем создавать команды с пустыми именами или цветами форм. Если бы это было реальное приложение, то нам необходимо было принять некоторые обеспечительные меры от плохих пользовательских данных. Подобный «промах» с проверкой данных был нами намеренно оставлен, ибо в Главе №5 мы изучим способы валидации данных.
    Закройте приложение, запустите снова… наслаждайтесь вашими трудами… но, мы закончили только работу с командами, а с игроками еще предстоит поработать. Этим мы и займемся в следующем разделе.

    Пользовательский интерфейс для управления игроками


    Для реализации пользовательского интерфейса управления игроками нам понадобится два экрана и соответствующих контроллера: один для отображения списка игроков в команде, а второй для добавления нового игрока или редактирования уже существующего. Эти контроллеры в больше части являются зеркальными копиями контроллеров для работы с командами, хотя они и не содержат NSFetchedResultsController и остальной код для работы с Core Data, вместо этого они делегируют взаимодействие с Core Data MasterViewController.

    Создадим сперва контроллер и экран для отображения списка игроков команды. Создаем новый контроллер PlayerListViewController, устанавливаем его родительский класс UITableViewController и снимаем галочку с опции «With XIB for user interface». Открываем файл PlayerListViewController.h. Этот класс отвечает за отображение списка игроков команды, а значит в этом классе необходима ссылка на объект команды. Так же, с учетом того, что данный класс делегирует взаимодействие с Core Data контроллеру MasterViewController, то необходима еще и ссылка на сам контроллер.
    На экране будет кнопка + для добавления нового игрока. Объявим соответствующий метод-обработчик нажатия.

    PlayerListViewController.h
    #import <UIKit/UIKit.h>
    @class MasterViewController;
    @interface PlayerListViewController : UITableVIewController {
       NSManagedObject *team;
       MasterViewController *masterViewController;
    }
    @property (nonatomic, retain) NSManagedObject *team;
    @property (nonatomic, retain) MasterViewController *masterController;
    - (id)initWithMasterController:(MasterViewController *)aMasterController team:(NSManagedObject *)aTeam;
    - (void)showPlayerView;
    - (NSArray *)sortPlayers;
    @end
    

    Откройте файл PlayerListViewController.m и импортируйте MasterVIewController.h, синтезируйте свойства team и masterController. Измените сгенерированный метод initWithStyle: на initWithMasterController:, который принимает два свойства и сохраняет следующим образом:
    - (id)initWithMasterController:(MasterVIewController *)aMasterVIewController team:(NSManagedObject *)aTeam {
       if((self = [super init])){
          self.masterController = aMasterController;
          self.team = aTeam;
       }
       return self;
    }
    

    Сгенерированный автоматически метод viewDidLoad изменим следующим образом:
    - (void)viewDidLoad{
       [super viewDidLoad];
       self.title = @"Player";
       UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(showPlayerView)];
       self.navigationItem.rightBarButtonItem = addButton;
    }
    

    А метод showPlayerView пока оставим пустым:
    - (void)showPlayerView{
    }
    

    Подкорректируем метод viewWillAppear: следующим образом:
    - (void)viewWillAppear:(BOOL)animated{
       [super viewWillAppear:animated];
       [self.tableView reloadData];
    }
    

    Список игроков в таблице будет отсортирован в алфавитном порядке в единственной секции (разделе). Для того, чтобы получить список всех игроков команды необходимо вызвать метод valueForKey:@"players" объекта команды, который вернет нам NSSet* игроков. Ниже представлен код для настройки отображения таблицы:
    - (NSUInteger)numberOfSectionInTableView:(UITableView *)tableView {
       return 1;
    }
    - (NSInteger)tableView:(UITableVIew *)tableView numberOfRowsInSection:(NSInteger)section
    {
       return [(NSSet *)[team valueForKey:@"players"] count];
    }
    - (UITableViewCell *)tableView:(UITableVIew *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
       static NSString *CellIdentifier = @"PlayerCell";
       UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
       if(cell == nil){
          cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
       }
       NSManagedObject *player = [[self sortPlayers] objectAtIndex:indexPath.row];
       cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", [[player valueForKey:@"firstName"] description], [[player valueForKey:@"lastName"] description]];
       cell.detailTextLabel.text = [[player valueForKey:@"email"] description];
       return cell;
    }
    

    Выше есть вызов метода sortPlayers, который возвращает отсортированный массив игроков:
    - (NSArray *)sortPlayers{
       NSSortDescriptor *sortLastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastName" ascending:YES];
       NSArray *sortDescriptors = [NSArray arrayWithObjects:sortLastNameDescriptor, nil];
       return [[(NSSet *)[team valueForKey:@"players"] allObjects] sortedArrayUsingDescriptors:sortDescriptors];
    }
    

    Для того, чтобы отображать список игроков команды вернемся в MasterViewController.m и добавим метод, который будет обрабатывать нажатие на дополнительный аксессуар (синий элемент ячейки):
    - (void)tableView:(UITableVIew *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{
       NSManagedObject *team = [self.fetchedResultsController objectAtIndexPath:indexPath];
       PlayerListViewController *playerListViewController = [[PlayerListViewController alloc] initWithMasterController:self team:team];
       [self.navigationController pushViewController:playerListViewController animated:YES];
    }
    


    Добавьте импорт PlayerListViewController.h в MasterViewController.m. Теперь соберем и запустим приложение. В списке команд мы видим ранее созданные нами команды, при нажатии на аксессуар-кнопку открывается список игроков выбранной команды (пока списки игроков пусты, ибо мы не реализовали добавление нового игрока).

    img

    Добавление, редактирование и удаление игроков


    Приложение уже почти готово; единственное, что стоит реализовать, так это добавление/редактирование/удаление игроков.
    Создадим новый контроллер (UIViewController) c соответствующим XIBом и назовем его PlayerViewController. Он будет похож на TeamViewController, но содержать три поля: lastName, firstName и email. Контроллер так же содержит ссылку на MasterViewController для того, чтобы иметь возможность использовать написанные ранее методы для работы с Core Data. Так же будут присутствовать еще два свойства: команды в которой играет игрок и сам игрок. Если объект игрока равен nil, то PlayerViewController знает, что необходимо создать нового игрока, в противном случае — редактирование данных игрока. На экране у нас будет три кнопки: сохранить, отмена и удалить. При запросе на удаление игрока мы будем запрашивать подтверждение у пользователя в виде отображения UIActionSheeta, поэтому необходимо, чтобы PlayerViewController реализовывал методы протокола UIActionSheetDelegate.

    PlayerViewController.h
    #import <UIKit/UIKit.h>
    @class MasterViewController;
    @interface PlayerViewController : UIViewController <UIActionSheetDelegate> {
       IBOutlet UITextField *firstName;
       IBOutlet UITextField *lastName;
       IBOutlet UITextField *email;
       NSManagedObject *team;
       NSManagedObject *player;
       MasterViewController *masterViewController;
    }
    @property (nonatomic, retain) UITextField *firstName;
    @property (nonatomic, retain) UITextField *lastName;
    @property (nonatomic, retain) UITextField *email;
    @property (nonatomic, retain) NSManagedObject *team;
    @property (nonatomic, retain) NSManagedObject *player;
    @property (nonatomic, retain) MasterViewController *masterController;
    - (IBAction)save:(id)sender;
    - (IBAction)cancel:(id)sender;
    - (IBAction)confirmDelete:(id)sender;
    - (id)initWithMasterController:(MasterViewController *)aMasterController team:(NSManagedObject *)aTeam player:(NSManagedObject *)aPlayer;
    @end
    

    Теперь откройте PlayerViewController.m, импортируйте MasterViewController.h и добавьте @synthesize для всех свойств из интерфейса. Добавьте метод инициализации в PlayerVIewController.m, который будет получать экземпляр класса MasterViewController, команду и возможно объект игрока.
    - (id)initWithMasterController:(MasterViewController *)aMasterController team:(NSManagedObject *)aTeam player:(NSManagedObject *)aPlayer{
       if((self = [super init])){
          self.masterController = aMasterController;
          self.team = team;
          self.player = player;
       }
       return self;
    }
    

    В метод viewDidLoad добавим код для заполнения текстовых полей данными игрока, если он не равен nil.
    - (void)viewDidLoad {
       [super viewDidLoad];
       if(player != nil){
          firstName.text = [player valueForKey:@"firstName"];
          lastName.text = [player valueForKey:@"lastName"];
          email.text = [player valueForKey:@"email"];
       }
    }
    

    Следующим нашим шагом будет реализация методов-обработчиков нажатий на кнопки:
    - (IBAction)save:(id)sender{
       if(masterController != nil){
          if(player != nil){
             [player setValue:firstName.text forKey:@"firstName"];
             [player setValue:lastName.text forKey:@"lastName"];
             [player setValue:email.text forKey:@"email"];
          } else {
             [masterController insertPlayerWithTeam:team firstName:firstName.text lastName:lastName.text email:email.text];
          }
       }
       [self dismissModelViewControllerAnimated:YES];
    }
    - (IBAction)cancel:(id)sender{
       [self dismissModalViewControllerAnimated:YES];
    }
    

    Метода insertPlayerWithTeam:firstName:lastName:email: в контроллере MasterViewController пока нет, но мы его напишем буквально через пару минут. Сперва реализуем confirmDelete: метод, который вызывается при нажатии на кнопку «Delete»(Удалить). Этот метод не будет сразу удалять игрока, а он запросит у пользователя подтверждения на выполнение данного действия (делается для того, чтобы избежать случайных нажатий и удалений игроков). Вот как будет выглядеть метод confirmDelete::
    - (IBAction)confirmDelete:(id)sender{
       if(player != nil){
          UIActionSheet *confirm = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete Player" otherButtonTitles:nil];
          confirm.actionSheetStyle = UIActionSheetStyleBlackTranslucent;
          [confirm showInView:self.view];
       }
    }
    

    Делегатом, который будет обрабатывать действия совершенные в UIActionSheetе будет текущий класс. При нажатии на кнопки UIActionSheetа будет вызываться метод clickedButtonAtIndex:, а значит необходимо его реализовать. В методе будет проверка на то какая кнопка была нажата и, если кнопка Delete, то будет вызван метод (который мы так же потом реализуем) удаления игрока:
    - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
       if(buttonIndex == 0 && masterController != nil){
          [masterController deletePlayer:player];
          [self dismissModalViewControllerAnimated:YES];
       }
    }
    

    Вернемся теперь к MasterViewController.h и объявим два метода, которые мы еще не реализовывали, но уже использовали:
    - (void)insertPlayerWithTeam:(NSManagedObject *)team firstName:(NSString *)firstName lastName:(NSString *)lastName email:(NSString *)email;
    - (void)deletePlayer:(NSManagedObject *)player;
    

    Открываем теперь MasterViewController.m и реализуем методы:
    - (void)insertPlayerWithTeam:(NSManagedObject *)team firstName:(NSString *)firstName lastName:(NSString *)lastName email:(NSString *)email{
       NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
       NSManagedObject *player = [NSEntityDescription insertNewObjectForEntityForName:@"Player" inManagedObjectContext:context];
       [player setValue:firstName forKey:@"firstName"];
       [player setValue:lastName forKey:@"lastName"];
       [player setValue:email forKey:@"email"];
       [player setValue:team forKey:@"team"];
       [self saveContext];
    }
    - (void)deletePlayer:(NSManagedObject *)player{
       NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
       [context deleteObject:player];
       [self saveContext];
    }
    


    Последним шагом является создание пользовательского экрана для добавления/редактирования игрока. Выберите PlayerViewController.xib, приведите его к такому виду, как на картинке ниже и соедините все Action с соответствующими кнопками.

    img


    Для того, чтобы отобразить этот экран необходимо вернуться к реализации метода showPlayerView:, который мы ранее использовали. Импортируйте в PlayerListViewController.m файл PlayerViewController.h.
    - (void)showPlayerVIew{
       PlayerVIewController *playerViewController = [[PlayerVIewController alloc] initWithMasterController:masterController team:team player:nil];
       [self presentModalViewController:playerVIewController animated:YES];
    }
    

    Нам также необходимо обрабатывать нажатия на ячейки таблицы списка игроков. Находим в PlayerListViewController.m автоматически сгенерированный метод didSelectRowAtIndexPath: и приводим его к следующему виду:
    - (void)tableView:(UITableVIew *)tableVIew didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
       NSManagedObject *player = [[self sortPlayers] objectAtIndex:indexPath.row];
       PlayerViewController *playerViewController = [[PlayerVIewController alloc] initWithMasterController:masterController team:team player:player];
       [self presentModalVIewController:playerViewController animated:YES];
    }
    


    На это реализация приложения управления командами и игроками завершена. Запустите приложение. Добавьте игроков, удалите игроков, удалите команды с игроками.

    Оставшиеся секции для перевода. Скоро будут.

    • Проверка данных хранилища
    • Использование хранилища данных в памяти (In-memory persistent store)
    • Разработка собственного типа хранилища
    • Инициализация настраиваемого хранилища
    • Осуществление маппинга между NSManagedObject и NSAtomicStoreCacheNode
    • Сериализация данных
    • Использование настраиваемого (кастомного) хранилища
    • Что насчет XML-типа хранилища?
    • Заключение

    Автор: AndrewShmig