NSTokenField, представляющий отношение базовых данных ко многим - PullRequest
7 голосов
/ 07 октября 2010

У меня проблема с выяснением того, как представить модель отношений «многие ко многим» в NSTokenField. У меня есть две (соответствующие) модели:

Пункт Тэг

Элемент может иметь много тегов, а тег может иметь много элементов. Так что это обратное отношение ко многим.

То, что я хотел бы сделать, это представить эти теги в NSTokenField. Я хотел бы закончить с токеном, автоматически предлагающим совпадения (нашел способ сделать это с помощью tokenfield: завершенийForSubstring: indexOfToken: indexOfSelectedItem) и возможностью добавлять новые теги, если они не были сопоставлены с существующим. *

Хорошо, надеюсь, ты все еще со мной. Я пытаюсь сделать все это с помощью привязок и контроллеров массива (так как это имеет смысл, верно?)

У меня есть контроллер массива "Item Array Controller", который привязан к делегатам моего приложения managedObjectContext. Таблица, показывающая все элементы, имеет привязку к этому контроллеру массива.

Значение моего NSTokenField имеет привязку к ключу выбора контроллеров массива и пути ключа модели: теги.

С этим конфигом NSTokenField не будет показывать теги. Это просто дает мне:

<NSTokenFieldCell: 0x10014dc60>: Unknown object type assigned (Relationship objects for {(
    <NSManagedObject: 0x10059bdc0> (entity: Tag; id: 0x10016d6e0 <x-coredata://9D77D47A-1171-4397-9777-706F599D7E3B/Tag/p102> ; data: <fault>)
)} on 0x100169660).  Ignoring...

Это имеет смысл для меня, так что не беспокойтесь. Я посмотрел на некоторые методы делегата NSTokenField, и мне кажется, что я должен использовать:

- (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject

Проблема в том, что этот метод не вызывается, и я получаю ту же ошибку, что и раньше.

Хорошо, мой следующий шаг - попытаться создать ValueTransformer. Преобразование из массива с тегом лица -> массив со строками (именами тегов) было хорошо. Другой способ более сложный.

Я пытался найти каждое имя в контексте управляемого объекта делегата моего общего приложения и вернуть соответствующие теги. Это вызывает у меня проблему с различными контекстами управляемого объекта:

Illegal attempt to establish a relationship 'tags' between objects in different contexts (source = <NSManagedObject: 0x100156900> (entity: Item; id: 0x1003b22b0 <x-coredata://9D77D47A-1171-4397-9777-706F599D7E3B/Item/p106> ; data: {
author = "0x1003b1b30 <x-coredata://9D77D47A-1171-4397-9777-706F599D7E3B/Author/p103>";
createdAt = nil;
filePath = nil;
tags =     (
);
title = "Great presentation";
type = "0x1003b1150 <x-coredata://9D77D47A-1171-4397-9777-706F599D7E3B/Type/p104>";
}) , destination = <NSManagedObject: 0x114d08100> (entity: Tag; id: 0x100146b40 <x-coredata://9D77D47A-1171-4397-9777-706F599D7E3B/Tag/p102> ; data: <fault>))

Куда я иду не так? Как мне решить это? Это даже правильный подход (мне кажется странным, что вы должны использовать ValueTransformer?)

Заранее спасибо!

Ответы [ 3 ]

7 голосов
/ 19 апреля 2011

Я написал пользовательский NSValueTransformer для сопоставления между NSManagedObject/Tag NSSet и NSString NSArray поля токена. Вот 2 метода:

- (id)transformedValue:(id)value {
  if ([value isKindOfClass:[NSSet class]]) {
    NSSet *set = (NSSet *)value;
    NSMutableArray *ary = [NSMutableArray arrayWithCapacity:[set count]];
    for (Tag *tag in [set allObjects]) {
      [ary addObject:tag.name];
    }
    return ary;
  }
  return nil;
}

- (id)reverseTransformedValue:(id)value {
  if ([value isKindOfClass:[NSArray class]]) {
    NSArray *ary = (NSArray *)value;
    // Check each NSString in the array representing a Tag name if a corresponding
    // tag managed object already exists
    NSMutableSet *tagSet = [NSMutableSet setWithCapacity:[ary count]];
    for (NSString *tagName in ary) {
      NSManagedObjectContext *context = [[NSApp delegate] managedObjectContext];
      NSFetchRequest *request = [[NSFetchRequest alloc] init];

      NSPredicate *searchFilter = [NSPredicate predicateWithFormat:@"name = %@", tagName];
      NSEntityDescription *entity = [NSEntityDescription entityForName:[Tag className] inManagedObjectContext:context];

      [request setEntity:entity];
      [request setPredicate:searchFilter];

      NSError *error = nil;
      NSArray *results = [context executeFetchRequest:request error:&error];
      if ([results count] > 0) {
        [tagSet addObjectsFromArray:results];
      }
      else {
        Tag *tag = [[Tag alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
        tag.name = tagName;

        [tagSet addObject:tag];
        [tag release];
      }
    }
    return tagSet;
  }
  return nil;
}

Кажется, что CoreData автоматически устанавливает объектные отношения при возврате (но я еще не полностью проверил это)

Надеюсь, это поможет.

1 голос
/ 19 апреля 2011

Ваша вторая ошибка вызвана наличием двух отдельных контекстов управляемого объекта с одной моделью и активным хранилищем одновременно.Вы пытаетесь создать объект в одном контексте, а затем связать его с другим объектом во втором контексте.Это не разрешено.Вы должны потерять второй контекст и сделать все ваши отношения в одном контексте.

Ваша первоначальная ошибка вызвана неполным путём ввода.Из вашего описания звучит так, будто вы пытаетесь заполнить поля токена ItemsArrayController.selectedItem.tags, но это просто вернет объект Tag, который не может быть использован в поле токена.Вместо этого вам нужно предоставить что-то, что преобразуется в строку, например ItemsArrayController.selectedItem.tags.name

0 голосов
/ 24 февраля 2011

2 вопроса:

1) У вас есть NSManagedObjectContext, который используется помимо контекста вашего приложения? 2) Является ли объект, который реализует tokenField: displayStringForRepresentedObject: установлен в качестве делегата для NSTokenField?

...