Question

I have a ad layout program which builds pages (NSView class Page) and on each page places margins and gutters (more NSViews) and then ads (NSView class Ad). The app auto places ads from a database source, looking for the proper space to fill on each page. The user then has the option to move the ads from say page 4 to page 2. They also have the option to manually flow the ads or have the app autoflow, the assumption being that the ad you just moved will end up in the 0/0 x/y location and then the others will flow to any available space, being pushed to the next page if there is no space on the current one (due to the ad you just moved).

What I was doing was getting all the pages (themselves subviews of an NSView called masterpage) and then looping through them to get all of their subviews into an array. Loop through that array, filtering for margins and gutters and if it is an ad remove it from the superview. My thought was to start with a clean page and replace all the ads. Problem is I keep getting this error:

Collection <NSCFArray: 0x1078900> was mutated while being enumerated.

I'm using this code:

//loop through all the pages
    for(NSView* thisPage in masterPageSubViews) { 

        //get all the ads on this page
        NSArray* thisPageSubviews = [thisPage subviews];
        for(NSView* thisPageSubview in thisPageSubviews) { 
            if ([[thisPageSubview className] isEqual:@"Ad"]) { 
                [thisPageSubview removeFromSuperview];
            }
        }
    }

I guess I am confused as to why I am getting this error. I thought the objects in the array were separate from the objects being created in the loop.

Since the number of ads on each page is dynamic how can I strip them from their superview without utilizing an array?

Was it helpful?

Solution

As the error says, you can't amend the contents of an array while going through it using fast enumeration. removeFromSuperview removes the view from its superview's subviews array (that is a hard sentence to read!). This means you are mutating the array while you are enumerating it.

There is probably a better way, but you could have an NSMutableArray which you populate with the Ad views, then once you've finished, you can make them remove themselves from the superview (untested code, type in browser!)

NSMutableArray *viewsToRemove = [[NSMutableArray alloc] init];
for(NSView* thisPage in masterPageSubViews) 
{           
    //get all the ads on this page         
    NSArray* thisPageSubviews = [thisPage subviews];         
    for(NSView* thisPageSubview in thisPageSubviews)              
        if ([thisPageSubview isMemberOfClass:[Ad class]]) 
            [viewsToRemove addObject:thisPageSubview];                                 
}         
[viewsToRemove makeObjectsPerformSelector:@selector(removeFromSuperview)];
[viewsToRemove release];  

OTHER TIPS

The above answer is rather expensive, why create objects when you can avoid it? And you can do the same thing with less code.

while([[superView subviews] count] > 0) {
    [[[superView subviews] objectAtIndex:0] removeFromSuperview];
}

EDIT: I'm new to Objective C and Cocoa, so I have no idea if this will works without ARC, as I've never been coding without it.

if([[yourView subviews] count] > 0) {
    [[[yourView subviews] objectAtIndex:0] removeFromSuperview];
}

Good Luck !!!

At least since 10.10 you can use fast enumeration on subviews since it returns a copy.

[view.subviews enumerateObjectsUsingBlock:^(NSView * subview, NSUInteger idx, BOOL *stop) {
    [subview removeFromSuperview];
}];

If checking the class is an issue (and in the original question it was), then, again without an enumerator, the problem can be solved from the back-end:

NSArray *subs = [myView subviews];
for(NSInteger i=[subs count]-1; i>0; i--){
    if([[subs[i] className]isEqual:@"nameOfClassToDelete"]){
        [subs[i] removeFromSuperviewWithoutNeedingDisplay];
    }
}

I have this code in practice, it works :-)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top