NSTokenField代表Core Data to-many关系

时间:2010-10-07 11:00:17

标签: objective-c cocoa core-data nstokenfield

我在弄清楚如何在NSTokenField中表示多对多关系模型时遇到了问题。我有两个(相关)模型:

物品 标签

项目可以包含多个标签,标签可以包含多个项目。所以这是一种反多种关系。

我想要做的是在NSTokenField中表示这些标签。我想最终得到一个令牌字段自动建议匹配(找到一种方法用tokenfield:completionsForSubstring:indexOfToken:indexOfSelectedItem),并能够添加新的标签实体,如果它不匹配现有的。

好的,希望你还和我在一起。我试图用绑定和数组控制器做这一切(因为这最有意义,对吧?)

我有一个数组控制器,“项目数组控制器”,它绑定到我的应用程序委托managedObjectContext。显示所有项目的tableview具有与此阵列控制器的绑定。

我的NSTokenField的值绑定了数组控制器选择键和模型键路径:tags。

使用此配置,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。从具有标记实体的数组转换 - &gt;带字符串的数组(标记名称)都很好。另一种方式更具挑战性。

我尝试过的是在我的共享应用委托管理对象上下文中查找每个名称并返回匹配的标记。这显然给我一个不同的托管对象上下文的问题:

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 个答案:

答案 0 :(得分:7)

我已经编写了一个自定义NSValueTransformer,用于在令牌字段的绑定NSManagedObject/Tag NSSetNSString NSArray之间进行映射。以下是两种方法:

- (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 :(得分:1)

您的第二个错误是由于两个单独的托管对象上下文具有相同的模型并且同时存储活动而引起的。您正尝试在一个上下文中创建一个对象,然后在第二个上下文中将其与另一个对象相关联。这是不允许的。您需要丢失第二个上下文并在单个上下文中建立所有关系。

您的初始错误是由不完整的密钥路径引起的。从您的描述中可以看出,您尝试使用ItemsArrayController.selectedItem.tags填充令牌字段,但这只会返回令牌字段无法使用的Tag对象。相反,您需要为其提供转换为字符串的内容,例如ItemsArrayController.selectedItem.tags.name

答案 2 :(得分:0)

2个问题:

1)您是否使用了除应用委托代理上下文之外的NSManagedObjectContext? 2)实现tokenField的对象:displayStringForRepresentedObject:设置为NSTokenField的委托吗?