Search code examples
iosobjective-ccocoa-touchuicollectionviewuicollectionviewlayout

UICollectionView Sections Overlapping


I'm having an issue whereby objects in my collection view collide with and interact with one another unintentionally.

Explanation:

I have a UICollectionView that I'm populating with custom UICollectionViewCell objects. This all works fine and well. I've also got headers to separate each section.

Now, one of the basic functionality of these buttons is to have an off state, and an on state.

The problem, though, is that when I turn a button "on" to something in my first section, it turns "on" or "off" a button in my last section, which appears off screen. In addition, the section titles sort of overlap as well. enter image description here

Here is the code that generates each cell:

Custom Cell class

- (id) initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.tagBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        self.tagBtn.frame = CGRectMake(-frame.size.width/4+20, -frame.size.width/4+15, frame.size.width, frame.size.height);
        [[self.tagBtn layer] setBorderColor:[[UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f] CGColor]];
        [[self.tagBtn layer] setBorderWidth:1.0f];
        self.tagBtn.backgroundColor = [UIColor whiteColor];
        [self.tagBtn setTitleColor:[UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f] forState:UIControlStateNormal];
        self.tagBtn.titleLabel.font = [UIFont systemFontOfSize:12.0f];
        [self.tagBtn addTarget:self action:@selector(toggleTag) forControlEvents:UIControlEventTouchUpInside];
        [self.contentView addSubview:self.tagBtn];

    }

    return self;
}

Cell data source delegate

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    TagCellCollectionViewCell *cell = (TagCellCollectionViewCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    NSArray *data = [dataArray objectAtIndex:indexPath.section];
    // Configure the cell

    NSString *labelText = data[indexPath.row];

    [cell.tagBtn setTitle:labelText forState:UIControlStateNormal];

    return cell;
}

Button toggle method

- (void) toggleTag {
    if (self.tagBtn.backgroundColor == [UIColor whiteColor]) {
        self.tagBtn.backgroundColor = [UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f];
        [self.tagBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    }
    else {
        self.tagBtn.backgroundColor = [UIColor whiteColor];
        [self.tagBtn setTitleColor:[UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f] forState:UIControlStateNormal];
    }
}

Section titles

- (UICollectionReusableView *) collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if (kind == UICollectionElementKindSectionHeader) {
        UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"Header" forIndexPath:indexPath];
        if (headerView == nil) {
            headerView = [[UICollectionReusableView alloc] initWithFrame:CGRectMake(5, 0, 320, 25)];
        }

        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(15, 0, 320, 25)];
        label.text = [NSString stringWithFormat:@"Section %li", indexPath.section];
        [label setTextColor:[UIColor colorWithRed:38.0f/255.0f green:34.0f/255.0f blue:108.0f/255.0f alpha:1.0f]];
        headerView.backgroundColor = [UIColor colorWithRed:204.0f/255.0f green:204.0f/255.0f blue:222.0f/255.0f alpha:1.0f];
        [headerView addSubview:label];
        return headerView;
    }
    return nil;
}

Is there anything else you'd like to see?

UPDATE

New button code with answer below

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    TagCellCollectionViewCell *cell = (TagCellCollectionViewCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    NSArray *data = [dataArray objectAtIndex:indexPath.section];
    // Configure the cell

    NSString *labelText = data[indexPath.row];

    [cell.tagBtn setTitle:labelText forState:UIControlStateNormal];
    [cell.tagBtn addTarget:self action:@selector(toggleTag:) forControlEvents:UIControlEventTouchUpInside];
    cell.tagBtn.tag = indexPath.row + 1000*indexPath.section;


    if (self.toggledIndexPath == indexPath) {
        cell.tagBtn.backgroundColor = [UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f];
        [cell.tagBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    }
    else {
        cell.tagBtn.backgroundColor = [UIColor whiteColor];
        [cell.tagBtn setTitleColor:[UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f] forState:UIControlStateNormal];

    }
    return cell;
}

- (void) toggleTag:(UIButton *) btn {
    NSIndexPath *path;
    if (btn.backgroundColor == [UIColor whiteColor]) {
        path = [NSIndexPath indexPathForItem:btn.tag/1000 inSection:btn.tag % 1000];
    }else{
        path = [NSIndexPath indexPathForItem:-1 inSection:0];
    }
    self.toggledIndexPath = path;
    [self.collectionView reloadData];
}

Solution

  • Your code has several problems having to do with cell and header view reuse. Any changes that you make to the cell's UI should be done inside cellForItemAtIndexPath, not outside like in your button's action method. If you do it in the action method, then when that cell is reused, the button will still have those changed values, but now in a different position in the collection view. You need to get the indexPath for the cell where you touched the button. There are several ways to do that, the easiest of which is to give your button a tag based on the indexPath (when you have sections, you need to use both, something like button.tag = indexPath.row + 1000*indexPath.section). In the button's action method, you should just set some property in your data source that indicates the button at a particular indexPath should have its properties changed, and then change them in an if-else clause in cellForItemAtIndexPath.

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        TagCellCollectionViewCell *cell = (TagCellCollectionViewCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
        NSArray *data = [dataArray objectAtIndex:indexPath.section];
        NSString *labelText = data[indexPath.row];
        [cell.tagBtn setTitle:labelText forState:UIControlStateNormal];
        [cell.tagBtn addTarget:self action:@selector(toggleTag:) forControlEvents:UIControlEventTouchUpInside];
        cell.tagBtn.tag = indexPath.row + 1000*indexPath.section;
        if (self.toggledIndexPath == indexPath) {
            cell.tagBtn.backgroundColor = [UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f];
            [self.tagBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        }else {
            cell.tagBtn.backgroundColor = [UIColor whiteColor];
            [cell.tagBtn setTitleColor:[UIColor colorWithRed:234.0f/255.0f green:99.0f/255.0f blue:74.0f/255.0f alpha:1.0f] forState:UIControlStateNormal];
        }
        return cell;
    }
    
    - (void) toggleTag:(UIButton *) btn {
        if (self.tagBtn.backgroundColor == [UIColor whiteColor]) {
            indexPath *path = [NSIndexPath indexPathForItem:btn.tag % 1000 inSection:btn.tag/1000];
        }else{
            indexPath *path = [NSIndexPath indexPathForItem:-1 inSection:0];
        }
        self.toggledIndexPath = path;
        [self.tableView reloadData];
    }
    

    Notice that I added the button's action method in cellForRowAtIndexPath rather than in the button's init method. toggledIndexPath is a property I added to keep track of which buttons was touched.

    The problem with the section titles, is that you're adding multiple labels to your headerView, because when viewForSupplementaryElementOfKind is called and it has a cell to reuse, you're still adding a label to it. You should either create your view in a xib, or add any subviews in the init method for the view (if you're doing everything in code). The only thing you should be doing in viewForSupplementaryElementOfKind, is to set the title and return the view.