简体   繁体   中英

Dynamic height collection/tag view inside a UITableViewCell

I have an already existing table view in my app which I'm trying to add a new cell to. The new cell will display a variable amount of buttons, each of which has a title (and thus a dynamic width). The new cell is supposed to look like this:

┃┃                                   ┃┃  <---- existing cells above
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃┏━━━━━┓  ┏━━━━━━━━━━━━━━━━━━━┓     ┃┃
┃┃┃ 632 ┃  ┃ 12345678901728933 ┃     ┃┃
┃┃┗━━━━━┛  ┗━━━━━━━━━━━━━━━━━━━┛     ┃┃
┃┃┏━━━━━━━━━━━┓  ┏━━━━━━┓  ┏━━━━━━┓  ┃┃
┃┃┃ 123345573 ┃  ┃ jhkl ┃  ┃ dasd ┃  ┃┃  <---- new cell
┃┃┗━━━━━━━━━━━┛  ┗━━━━━━┛  ┗━━━━━━┛  ┃┃
┃┃┏━━━┓  ┏━━━━━━━━━━━━━━┓  ┏━━━┓     ┃┃
┃┃┃ 1 ┃  ┃ 128732434554 ┃  ┃ 1 ┃     ┃┃
┃┃┗━━━┛  ┗━━━━━━━━━━━━━━┛  ┗━━━┛     ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃                                   ┃┃  <---- existing cells below

The buttons should file on one line until the next one would overflow the cell, at which point it should wrap on to the next line (similar to a UILabel with word wrapping).

Important considerations

  1. This cell needs to have a dynamic height. I return UITableViewAutomaticDimension from my tableView:heightForRowAtIndexPath method and the cell needs to calculate its own height.
  2. The button title data will get populated before returning the cell from cellForRowAtIndexPath .
  3. I'm trying to find a solution that doesn't need to call beginUpdates/endUpdates on the table view to resize its cells.
  4. I'd prefer to do this using Autolayout but I'm willing to manually calculate frames if I really need to (though I'm not completely sure how to do that, so might need some guidance).
  5. This view needs to be usable outside of a table view cell as well, so I can't just look at [UIScreen mainScreen] to calculate the ideal width of the cell.

What I've tried already

  1. I tried implementing the view with a UICollectionView. In this case, the collection view's data source returns the buttons wrapped in UICollectionViewCells. The problem with this is that UICollectionView is a UIScrollView, and thus does not automatically resize itself to fit its content.
  2. I tried using a custom UICollectionView subclass with a custom implementation of intrinsicContentSize . This implementation would return the collection view's contentSize , or the collection view's UICollectionViewFlowLayout's collectionViewContentSize() (I tried both). The issue here is that these values don't return the actual content size until after the collectionView has been laid out, at which point my UITableView has already cached the cell height as 0.
  3. I tried using a UICollectionView and keeping an NSLayoutConstraint for the height of it, which would change every time the contentSize changed. Unfortunately this is subject to the same problem as above, since UITableView lays out the cell before the contentSize is changed.
  4. I tried implementing an entirely custom cell using nested UIStackViews: in this case, I would have N horizontal stack views inside the cell. I would add buttons to the first stack view until their total width exceeded the cell's bounds, at which point I would add them to the next stack view instead, etc. The problem is that in order to do this, I need to know the cell's bounds, which are not set until its layoutSubviews is called, at which point UITableView has already cached the height of the cell.
  5. I tried using something like https://github.com/xhacker/TagListView where the custom view manually calculates the frames of everything when layoutSubviews is called and reports (the number of rows) * (the height of each row) as its intrinsic content height, but the problem is that by the time this calculation happens, the frame of the view is 0 (since it's being laid out inside a UITableViewCell) and thus the intrinsic content size is calculated wrong, resulting in something like this:

     ┃┃ ┃┃ <---- existing cells above ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃ ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ ┃┃┏━━━━━┓ ┏━━━━━━━━━━━━┓ ┃┃ ┃┃┃ 632 ┃ ┃ 6327237842 ┃ ┃┃ ┃┃┗━━━━━┛ ┗━━━━━━━━━━━━┛ ┃┃ ┃┃┏━━━━━━━━━━━━━━━━━━━┓ ┃┃ ┃┃┃ 12345678901728933 ┃ ┃┃ ┃┃┗━━━━━━━━━━━━━━━━━━━┛ ┃┃ ┃┃ ┃┃ ┃┃ ┃┃ <---- new cell ┃┃ ┃┃ ┃┃ ┃┃ ┃┃ ┃┃ ┃┃ ┃┃ ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃ ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ ┃┃ ┃┃ <---- existing cells below 

In general, UITableView caching the height of the cell causes there to be a constraint called UIView-Encapsulated-Layout-Height, which has a constant of 0, and is well documented in many SO posts. As far as I can tell there's no way to re-calculate this height from the cell level: setNeedsUpdateConstraints , setNeedsLayout , layoutIfNeeded , invalidateIntrinsicContentSize , and any combination thereof that I've tried have not helped.

This has been really baffling me for the past two days. I'm wondering if anyone has implemented anything like this before and has found a good way to do it reliably, or can offer any advice as to how to tackle this problem.

Write this in table cellforRowAtIndexPath method.

        y=20;
        x=20;

        for (int i=0; i<[arrAssignUsers count]; i++) {
            CGRect screenRect=[[UIScreen mainScreen]bounds];
            CGFloat screenWidth=screenRect.size.width;


            [arrBtnStatus addObject:[NSNumber  numberWithBool:NO]];

            NSString *strNames=[arrAssignUsers objectAtIndex:i];
            stringsize=[strNames sizeWithAttributes:
                        @{NSFontAttributeName: [UIFont systemFontOfSize:20.0f]}];
            btn=[UIButton buttonWithType:UIButtonTypeRoundedRect];
            CGFloat m=x+stringsize.width+10;
            CGFloat n=screenWidth-20;

            if (m<=n) {
                btn.frame=CGRectMake(x, y,stringsize.width,stringsize.height);
                x=x+stringsize.width +10;
            }
            else
            {
                y=y+stringsize.height+10;
                x=20;
                btn.frame=CGRectMake(x, y,stringsize.width,stringsize.height);
                x=x+stringsize.width+10;
            }

            [btn setTitle:self.arrAssignUsers[i] forState:UIControlStateNormal];
            [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            btn.tag=i;
            [btn addTarget:self
                    action:@selector(notifyButtonPressedInEditTask:) forControlEvents:UIControlEventTouchUpInside];

            btn.backgroundColor=[UIColor whiteColor];
            btn.layer.cornerRadius=10;
            [btn.layer setMasksToBounds:YES];
            btn.layer.borderWidth=2.0f;
            btn.layer.borderColor=[UIColor orangeColor].CGColor;

            [self checkNotifybtnStatus:btn];
            [cell addSubview:btn];
            return cell;

Then write this method:

-(void)checkNotifybtnStatus:(UIButton *)clicked{
state = [[arrBtnStatus objectAtIndex:clicked.tag]boolValue];

if ((clicked.tag>=0)&&(state==YES)) {
    clicked.backgroundColor=OS_ORANGE_COLOR;
}
else
    clicked.backgroundColor=[UIColor clearColor];
}

Then write this.

-(void)notifyButtonPressedInEditTask:(id)sender{
UIButton *clicked = (UIButton *) sender;
NSLog(@"%ld",(long)clicked.tag);    //Here you know which button has pressed


for (UserListData *taskData in arrAssignUserInfoList) {
    [arrNotifyUsers addObject:taskData.strUniqID];
}

 state = [[arrBtnStatus objectAtIndex:clicked.tag]boolValue];

if ((clicked.tag>=0)&&(state==NO)) {
    [arrBtnStatus replaceObjectAtIndex:clicked.tag withObject:[NSNumber numberWithBool:!state]];
    clicked.backgroundColor=[UIColor colorWithRed:254.0/255.0 green:139.0/255.0 blue:26.0/255.0 alpha:1];

    [arrNotifySelectedUsers addObject:[arrNotifyUsers objectAtIndex:clicked.tag]];
    NSLog(@" arr new--%@",arrNotifySelectedUsers.description);
}
else
{
    clicked.backgroundColor=[UIColor clearColor];
    [arrBtnStatus replaceObjectAtIndex:clicked.tag withObject:[NSNumber numberWithBool:!state]];
    [arrNotifySelectedUsers removeObject:[arrNotifyUsers objectAtIndex:clicked.tag]];
    NSLog(@" arr new--%@",arrNotifySelectedUsers.description);
}
}

Change the variable according to your project. This will may help you.

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