简体   繁体   中英

NSTreeview Duplicating my objects

My very early steps in OS X development.

NSTreeview bound to an array which includes various proxy objects of custom class. Some of those objects are bound to my Core Data Store so that my sidebar includes lists of several different NSManagedObject entities in groups. For some reason the moc entities are duplicated, each listed twice. like so:

绑定到NSManangedObjects的组在列表中重复

NSTreeController is bound to NSArray, sidebarItems, which is populated here:

- (void)populateOutlineContents
{
    // hide the outline view - don't show it as we are building the content
    [self.myOutlineView setHidden:YES];
    BrowserItem *dashboardItem = [BrowserItem itemWithTitle:@"Home"];
    dashboardItem.nodeIcon = [NSImage imageNamed:@"home"];
    BrowserItem *todayItem = [BrowserItem itemWithTitle:@"Today"];
    BrowserItem *thisWeekItem = [BrowserItem itemWithTitle:@"This Week"];
    BrowserItem *separatorItem = [BrowserItem itemWithTitle:nil];

    // Setup Projects
    self.projectsSidebarGroup = [BrowserItem itemWithTitle:PROJECTS_NAME];
    self.projectsSidebarGroup.childItemTitleKeypath = @"projectName";
    [self.projectsSidebarGroup bindChildItemsToArrayKeypath:@"projectsArrayController.arrangedObjects" onObject:self];

    // Setup Crewmen
    self.crewmenSidebarGroup = [BrowserItem itemWithTitle:CREWMEN_NAME];
    self.crewmenSidebarGroup.childItemTitleKeypath = @"fullName";
    [self.crewmenSidebarGroup bindChildItemsToArrayKeypath:@"crewmenArrayController.arrangedObjects" onObject:self];


    self.sidebarItems = @[dashboardItem, todayItem, thisWeekItem,separatorItem, self.projectsSidebarGroup, self.crewmenSidebarGroup];

    [self.myOutlineView setHidden:NO];  // we are done populating the outline view content, show it again
}

In viewDidLoad I set up the Core Data bindings and NSArrayControllers for each group:

    [self populateOutlineContents];

    NSPredicate *ActiveProjectsFilter = [NSPredicate predicateWithFormat:@"inactive == NO"];
    NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:@"projectName" ascending:YES];
    self.projectsArrayController = [self controllerForEntity:@"Project" filter:ActiveProjectsFilter sortBy:@[sortByName]];

    NSPredicate *activeCrewmenFilter = [NSPredicate predicateWithFormat:@"Active == YES"];
    NSSortDescriptor *sortByLast = [[NSSortDescriptor alloc] initWithKey:@"nameLast" ascending:YES];
    NSSortDescriptor *sortByFirts = [[NSSortDescriptor alloc] initWithKey:@"nameFirst" ascending:YES];
    self.crewmenArrayController = [self controllerForEntity:@"WorkMan" filter:activeCrewmenFilter sortBy:@[sortByLast, sortByFirts]];

The actual controllers are setup here:

-(NSArrayController *)controllerForEntity:(NSString *)aEntityName filter:(NSPredicate *)filter sortBy:(NSArray *)sortDescriptors
{
    NSArrayController *controller = [[NSArrayController alloc] init];
    controller.managedObjectContext = self.managedObjectContext;
    controller.entityName = aEntityName;
    controller.automaticallyRearrangesObjects = YES;
    controller.automaticallyPreparesContent = YES;

    if (filter) {
        controller.fetchPredicate = filter;
    }

    if (sortDescriptors) {
        controller.sortDescriptors = sortDescriptors;
    }

    NSError *error;
    if (![controller fetchWithRequest:nil merge:NO error:&error])
    {
        ALog(@"Error fetching %@", aEntityName);
    }

    return controller;
}

I checked the state of my NSArrayControllers, arrangedObjects, there are only the expected number of items there. No duplicates actually exist so where are the duplicates in the OutlineView coming from?

My proxy object, in case it helps:

#import "BrowserItem.h"

@implementation BrowserItem
+(instancetype)itemWithTitle:(NSString *)title
{
    BrowserItem *item = [[BrowserItem alloc] init];
    [item setTitle:title];

    return item;
}

- (instancetype)init
{
    if (self = [super init])
    {
        self.children = [NSMutableArray array];
        self.modelToItemMapping = [NSMapTable weakToStrongObjectsMapTable];
        self.childItemsArrayController = [[NSArrayController alloc] init];

        [self.childItemsArrayController addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionPrior context:NULL];
    }
    return self;
}

- (void)bindChildItemsToArrayKeypath:(NSString*)keypath onObject:(id)object;
{
    [self.childItemsArrayController bind:@"contentArray" toObject:object withKeyPath:keypath options:nil];
    self.isGroup = YES;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"arrangedObjects"])
    {
        [self willChangeValueForKey:@"children"];
        NSMutableArray *watchableChildren = [self mutableArrayValueForKey:@"children"];

        [watchableChildren removeAllObjects];
        for (id modelItem in self.childItemsArrayController.arrangedObjects)
        {
            BrowserItem *browserItem = [self.modelToItemMapping objectForKey:modelItem];

            if (!browserItem)
            {
                browserItem = [BrowserItem itemWithTitle:[modelItem valueForKey:self.childItemTitleKeypath]];
                [self.modelToItemMapping setObject:browserItem forKey:modelItem];
            }
            [watchableChildren addObject:browserItem];
        }

        [self didChangeValueForKey:@"children"];
    }
}
@end

------------UPDATE---------

So, I changed from the NSMutableArrayForKey to creating a new NSMutableArray and replacing the children array when my objects are all set up. This eliminated the duplicate list however, There is still something afoul. I put some debug statements and counters in there and I can see that the routine is run 3 or 4 times on each of these groups. That seems unnecessary. I suppose it is not making duplicates because the previous results are now replaced by the latest run. Still would like to solve rather than leave as it is.

This is revised observer:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"arrangedObjects"])
    {
        [self willChangeValueForKey:@"children"];
        NSMutableArray *replacementChildren = [NSMutableArray array];

        for (id modelItem in self.childItemsArrayController.arrangedObjects)
        {
            BrowserItem *browserItem = [self.modelToItemMapping objectForKey:modelItem];

            if (!browserItem)
            {
                browserItem = [BrowserItem itemWithTitle:[modelItem valueForKey:self.childItemTitleKeypath]];
                [self.modelToItemMapping setObject:browserItem forKey:modelItem];
                [self.itemToModelMapping setObject:modelItem forKey:browserItem];
            }
            [replacementChildren addObject:browserItem];
        }
        _children = replacementChildren;
        [self didChangeValueForKey:@"children"];
    }
}

The duplicated items are probably caused by duplicate change notifications. didChangeValueForKey:@"children" and mutableArrayValueForKey both notify the observer. This will confuse the tree controller. Either use the willChangeValueForKey didChangeValueForKey combo or use mutableArrayValueForKey .

observeValueForKeyPath can be called multiple times. arrangedObjects of NSArrayController changes when an object is added or removed, when the contents is sorted and whenever NSArrayController thinks arrangedObjects has to be changed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM