Создадим новый проект:
Почитать о базовой шаблонной структуре Core Data проекта можно в посте "Из чего состоит Cocoa-приложение работающее с Core Data".
В этом посте мы рассмотрим как соединить NSOutlineView с Core Data моделью. Для этого есть несколько способов в том числе Cocoa Bindings.
Добавим обязательный атрибут name. По умолчанию он будет принимать значение New Group.
Добавим опциональные отношения parent и subGroups.
Идея реализации данной связи заключается в следующем. В таблице подразделения есть ключевое поле (ID) которое в данной таблице первичный ключ. На рисунке это запись с номером два. Этой записи может соответствовать много записей в таблице сотрудники. Значит в таблицу сотрудники нужно добавить поле в котором будет находиться первичный ключ таблицы подразделения. Подразделение, в котором работает человек - это просто атрибут этого человека. А список подразделений - это обычная таблица классификатор.
Давайте посмотрим пример связи многие-ко-многим. На предприятии есть работники, но и один работник может работать на двух предприятиях.
Вот это и есть связь многие-ко-многим (M-N). Как реализовать? Данная связь должна быть устранена путем замены двумя связями один-ко-многим (M-1, N-1). Это реализуется путем введения новой таблицы.
Можно сказать, что новая таблица является слабой сущностью, ее существования обусловлено только существованием двух других таблиц. Слабый тип то есть сущность зависит от другой. И сильный - это когда она не от кого не зависит.
Любое отношение (realationship) устанавливает связь между двумя сущностями.
У отношения parent установим свойству Destination значение Group, а свойству Inverse установим значение subGroups.
Аналогичным образом у отношения subGroups установим свойству Destination значение Group, а свойству Inverse установим значение parent. И ещё свойству Type нужно установить значение To Many.
У меня долго не получалось добиться отображения имени объекта в ячейке. Оказалось, что надо поставить свойству Content Mode значение Cell Based.
Дополнительно в целях эстетики я убрал Focus Ring для следующие объектов:
Managed Object Model представляет собой описание сущностей базы данных.
Managed Object Context - это контекст посредством которого осуществляется доступ к Core Data хранилищу, которое описано с помощью Managed Object Model.
Можно провести такую аналогию, например, мультфильм Симпсоны это хранилище данных. Вы можете посмотреть этот мультфильм по телеканалу, интернету или на DVD. При такой аналогии Managed Object Context - это метод с помощью которого вы будете смотреть мультфильм.
Для того чтобы добавленный Array Controller мог "смотреть" Groups, нам нужно указать контекст.
По шаблону проекта делегат приложения AppDelegate уже содержит свойство managedObjectContext. Остается только связать добавленный Array Controller с этой переменной. Это можно сделать в инспекторе Bindings. Надо выбрать Bind to Application и указать значение delegate.managedObjectContext для свойства Model Key Path.
Почитать о базовой шаблонной структуре Core Data проекта можно в посте "Из чего состоит Cocoa-приложение работающее с Core Data".
В этом посте мы рассмотрим как соединить NSOutlineView с Core Data моделью. Для этого есть несколько способов в том числе Cocoa Bindings.
1. Создание сущности в модели данных
Откроем нашу модель CoreData_NSOutlineView.xcdatamodeld и создадим новую сущность Group.Добавим обязательный атрибут name. По умолчанию он будет принимать значение New Group.
Добавим опциональные отношения parent и subGroups.
Проведем небольшой ликбез по отношениям в реляционных базах данных. Отношение - это связь между двумя сущностями. Например, есть сущности Сотрудник и Подразделение.
Видите, одной записи гараж соответствует много записей людей. Это соотношение называется один-ко-многим (1:M).
Идея реализации данной связи заключается в следующем. В таблице подразделения есть ключевое поле (ID) которое в данной таблице первичный ключ. На рисунке это запись с номером два. Этой записи может соответствовать много записей в таблице сотрудники. Значит в таблицу сотрудники нужно добавить поле в котором будет находиться первичный ключ таблицы подразделения. Подразделение, в котором работает человек - это просто атрибут этого человека. А список подразделений - это обычная таблица классификатор.
Давайте посмотрим пример связи многие-ко-многим. На предприятии есть работники, но и один работник может работать на двух предприятиях.
Вот это и есть связь многие-ко-многим (M-N). Как реализовать? Данная связь должна быть устранена путем замены двумя связями один-ко-многим (M-1, N-1). Это реализуется путем введения новой таблицы.
Можно сказать, что новая таблица является слабой сущностью, ее существования обусловлено только существованием двух других таблиц. Слабый тип то есть сущность зависит от другой. И сильный - это когда она не от кого не зависит.
Любое отношение (realationship) устанавливает связь между двумя сущностями.
У отношения parent установим свойству Destination значение Group, а свойству Inverse установим значение subGroups.
Аналогичным образом у отношения subGroups установим свойству Destination значение Group, а свойству Inverse установим значение parent. И ещё свойству Type нужно установить значение To Many.
2. Добавление и начальная настройка NSOutlineView
Переходим к интерфейсу. Добавьте на форму компонент NSOutlineView. Нам нужен только один столбец.У меня долго не получалось добиться отображения имени объекта в ячейке. Оказалось, что надо поставить свойству Content Mode значение Cell Based.
Дополнительно в целях эстетики я убрал Focus Ring для следующие объектов:
3. Добавление и настройка NSArrayController
Далее добавим NSArrayController. Нужно сделать следующее:- установить режим Mode на значение Entity;
- отметить пункт Prepares Content;
- указать наименование требуемой сущность Entity Name, в нашем случае это Group;
- указать предикат для извлечения экземпляров сущности: parent == nil (для начала нам нужен только корневой объект, а у него нет родителя).
Managed Object Model представляет собой описание сущностей базы данных.
Managed Object Context - это контекст посредством которого осуществляется доступ к Core Data хранилищу, которое описано с помощью Managed Object Model.
Можно провести такую аналогию, например, мультфильм Симпсоны это хранилище данных. Вы можете посмотреть этот мультфильм по телеканалу, интернету или на DVD. При такой аналогии Managed Object Context - это метод с помощью которого вы будете смотреть мультфильм.
Для того чтобы добавленный Array Controller мог "смотреть" Groups, нам нужно указать контекст.
По шаблону проекта делегат приложения AppDelegate уже содержит свойство managedObjectContext. Остается только связать добавленный Array Controller с этой переменной. Это можно сделать в инспекторе Bindings. Надо выбрать Bind to Application и указать значение delegate.managedObjectContext для свойства Model Key Path.
4. Добавление и настройка NSTreeController
Теперь добавим NSTreeController. В дальнейшем мы привяжем добавленный ранее NSOutlineView к данному контроллеру. Также как раньше нужно сделать следующее:- установить режим Mode на значение Entity;
- отметить пункт Prepares Content;
- указать наименование требуемой сущность Entity Name, в нашем случае это Group;
- установить для свойства Children значение subGroups (контроллер будет знать как обходить дерево).
Теперь для добавленного только что NSTreeController откроем Bindings Inspector. Привяжем его к добавленному ранее NSArrayController, по ключу arrangedObjects. Смотрите не напутайте, привязывать надо в секции Content Array.
У нас будет следующая схема. NSTreeController базируется на NSArrayController, который предоставляет управляемые объекты (managed objects), а NSTreeController отдает иерархическую структуру в NSOutlineView.
Во всплывающем окне надо выбрать пункт add:
OutlineViewController.h
OutlineViewController.m
В интерфейсе создадим экземпляр класса OutlineViewController. Свяжем свойства treeController и myOutlineView с соответствующими элементами в интерфейсе.
Для NSOutlineView установим источником данных наш экземпляр OutlineViewController.
Результат:
источник, мои исходники
У нас будет следующая схема. NSTreeController базируется на NSArrayController, который предоставляет управляемые объекты (managed objects), а NSTreeController отдает иерархическую структуру в NSOutlineView.
5. Заключительная настройка NSOutlineView
Вернемся к NSOutlineView. Надо привязать его единственный столбец к NSTreeController.6. Кнопка для добавления новых элементов
Добавим кнопку для добавления новых элементов. Свяжем её с NSArrayController.Во всплывающем окне надо выбрать пункт add:
7. Drag-and-drop
Создадим новый класс OutlineViewController.OutlineViewController.h
#import <Cocoa/Cocoa.h> @interface OutlineViewController : NSObject <NSOutlineViewDataSource> { IBOutlet NSTreeController *treeController; IBOutlet NSOutlineView *myOutlineView; NSArray *dragType; NSTreeNode *draggedNode; } @end
OutlineViewController.m
#import "OutlineViewController.h" @implementation OutlineViewController - (void)awakeFromNib { dragType = [NSArray arrayWithObjects: @"factorialDragType", nil]; [ myOutlineView registerForDraggedTypes:dragType ]; NSSortDescriptor* sortDesc = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; [treeController setSortDescriptors:[NSArray arrayWithObject: sortDesc]]; } - (BOOL) outlineView : (NSOutlineView *) outlineView writeItems : (NSArray*) items toPasteboard : (NSPasteboard*) pboard { [ pboard declareTypes:dragType owner:self ]; draggedNode = [ items objectAtIndex:0 ]; return YES; } - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index { NSManagedObject* draggedTreeNode = [ draggedNode representedObject ]; [ draggedTreeNode setValue:[item representedObject ] forKey:@"parent" ]; return YES; } - (BOOL) category:(NSManagedObject* )cat isSubCategoryOf:(NSManagedObject* )possibleSub { // Depends on your interpretation of subCategory .... if ( cat == possibleSub ) { return YES; } NSManagedObject* possSubParent = [possibleSub valueForKey:@"parent"]; if ( possSubParent == NULL ) { return NO; } while ( possSubParent != NULL ) { if ( possSubParent == cat ) { return YES; } // move up the tree possSubParent = [possSubParent valueForKey:@"parent"]; } return NO; } // This method gets called by the framework but // the values from bindings are used instead - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { return NULL; } - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index { // drags to the root are always acceptable if ( [item representedObject] == NULL ) { return NSDragOperationGeneric; } // Verify that we are not dragging a parent to one of it's ancestors // causes a parent loop where a group of nodes point to each other // and disappear from the control NSManagedObject* dragged = [ draggedNode representedObject ]; NSManagedObject* newP = [ item representedObject ]; if ( [ self category:dragged isSubCategoryOf:newP ] ) { return NO; } return NSDragOperationGeneric; } /* The following are implemented as stubs because they are required when implementing an NSOutlineViewDataSource. Because we use bindings on the table column these methods are never called. The NSLog statements have been included to prove that these methods are not called. */ - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { NSLog(@"numberOfChildrenOfItem"); return 1; } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { NSLog(@"isItemExpandable"); return YES; } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { NSLog(@"child of Item"); return NULL; } @end
В интерфейсе создадим экземпляр класса OutlineViewController. Свяжем свойства treeController и myOutlineView с соответствующими элементами в интерфейсе.
Для NSOutlineView установим источником данных наш экземпляр OutlineViewController.
Результат:
источник, мои исходники