In daily development, condition buttons and content TAB displays are often involved. There are a lot of attributes that need to be added, and it’s obviously too cumbersome and cumbersome to do them all with buttons

And it gets more complicated if these tags need to be set dynamically. This article implements these requirements through UICollectionView. Because the display content is dynamically controlled by the server, the first thing to do is to make the cell size of the collectionView adapt to the width of the text. Then you dynamically set the size of the collectionView.

Calculate the size of the cell

Since the code for calculating the width of the text is generic, the width and height of the cell are returned directly in the sizeForItemAtIndexPath method.

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    NSDictionary *attribute = @{NSFontAttributeName: self.textFont};
    CGSize currentLabelSize = [self.dataAry[indexPath.item] sizeWithAttributes:attribute];

    return CGSizeMake(ceil(currentLabelSize.width) + self.leftAndRightSpacing*2, self.itemCellHeight);
}

Copy the code

However, after setting, it may be found that the spacing of items is not fixed, as shown in the figure below:

Rewrite UICollectionViewFlowLayout method

To make collectionView neatly arranged, will use flowLayout, here again through the use of the inheritance UICollectionViewFlowLayout collectionView for layout. UICollectionViewFlowLayout is a layout class inherits from UICollectionViewLayout, mainly involves the following two methods:

- (CGSize)collectionViewContentSize; 

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;

Copy the code

CollectionViewContentSize this method, used to return collectionView content size, not UICollectionView frame size. LayoutAttributesForElementsInRect, here is to return all the layout attribute array. By overriding these two methods, you can rearrange the content so that the arrangement of each cell is compact.

First custom a class inherits from UICollectionViewFlowLayout, define the properties required

@interface LiveBroadcastFlowLayout : UICollectionViewFlowLayout / / @ / array content property (nonatomic, strong) NSArray * dataArry; ///item height @property (nonatomic, assign) CGFloat itemHeight; ///item left/right offset @property (nonatomic, assign) CGFloat itemWidthSpacing; @property (nonatomic, strong) UIFont *textFont; ///UICollectionView width @Property (nonatomic, assign) CGFloat contentWidth; @endCopy the code

Through code comments, you can understand the methods and functions of rewriting. Calculate the size of ContentSize:

- (CGSize)collectionViewContentSize{ CGSize size = CGSizeZero; NSInteger itemCount = 0; // Get the number of collectionView items. 0 to CGSizeZero if ([self. CollectionView. The dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]) { itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0]; } if (CGSizeEqualToSize(size, CGSizeZero) && itemCount == 0) { return CGSizeZero; } NSInteger lineWidth = 0; NSUInteger rowCount = 1; for (int i = 0; i < itemCount; Self. textFont calculates the item width based on the size of the font passed in. Self. contentWidth calculates the NSDictionary with the width of the collectionView passed in *attribute = @{NSFontAttributeName: self.textFont}; CGSize currentLabelSize = [self.dataArry[i] sizeWithAttributes:attribute]; CGFloat cellWidth = CurrentLabelSize. width + self.itemWidthSpacing*2; if (i == (itemCount - 1)) { lineWidth = lineWidth + cellWidth; } else { lineWidth = lineWidth + self.minimumInteritemSpacing + cellWidth; If (lineWidth > (NSInteger) self.contentwidth) {rowCount++; lineWidth = cellWidth + self.minimumInteritemSpacing + cellWidth; Size.width = self.contentWidth; size.height = rowCount * self.itemHeight + (rowCount - 1) * self.minimumLineSpacing + self.sectionInset.top + self.sectionInset.bottom; return size; }Copy the code

Control the display of the collectionView internal item layout:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray* attributes = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; // Set the first item to the upper left, To prevent a line only show one item when the position disorder if (attributes. Count > 0) {UICollectionViewLayoutAttributes * currentLayoutAttributes = attributes[0]; CGRect frame = currentLayoutAttributes.frame; frame.origin.x = 0; currentLayoutAttributes.frame = frame; } for(int i = 1; i < [attributes count]; ++i) { NSDictionary *attribute = @{NSFontAttributeName: self.textFont}; CGSize labelSize = [self.dataArry[i] sizeWithAttributes:attribute]; UICollectionViewLayoutAttributes *currentLayoutAttributes = attributes[i]; UICollectionViewLayoutAttributes *prevLayoutAttributes = attributes[i - 1]; CGFloat cellWidth = ceil(labelSize.width) + self.itemWidthSpacing*2; currentLayoutAttributes.size = CGSizeMake(cellWidth, self.itemHeight); NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame); // If the width of the current item + the maximum width of the previous item + the spacing between items <= the width of the collectionView, then a line can accommodate changing the X-axis position of the current item. Otherwise the left shows the if (origin + self. MinimumInteritemSpacing + currentLayoutAttributes. Frame. The size, width < self. ContentWidth) { CGRect frame = currentLayoutAttributes.frame; frame.origin.x = origin + self.minimumInteritemSpacing; currentLayoutAttributes.frame = frame; } else { CGRect frame = currentLayoutAttributes.frame; frame.origin.x = 0; currentLayoutAttributes.frame = frame; } } return attributes; }Copy the code

LayoutAttributesForElementsInRect method, here from the beginning of the second item, each cell location is the position of a cell + maximumSpacing before, if not more than the maximum width of the line, Change the start position and size of the current cell (also known as frame). If you go beyond that and you don’t change, what do you mean you don’t change? It is to keep the original position. The original position here may be different from what we expected, not the fixed position at the beginning, but the adjusted position, because the change of the previous cell here will have an impact on the following cell, and the position of the y axis at the back will be automatically adjusted.

Isn’t over

Most of the time UICollectionView is not used alone, but rather nested within the UITableView cell, which is responsible for the presentation of a small part of the content.

So how do you dynamically adjust the size and height of a UICollectionView in a UITableViewCell?

First of all,

Add the UICollectionView to the UITableViewCell and set the constraint:

In addition to the coordinate position, focus on setting UICollectionView’s width and height constraints, set any value here (close to the real size is ok).

then

Relate the width and height constraints via the XIB

@interface MyLiveBroadcastCollectView () <UICollectionViewDataSource, UICollectionViewDelegate>

@property (weak, nonatomic) IBOutlet LiveBroadcastFlowLayout *flowLayout;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureCollectionViewHeightCons;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureCollectionViewWidthCons;

@property (nonatomic, strong) NSArray *dataAry;

@end

Copy the code

Call the method em_displayWithID externally:

- (void)em_displayWithID:(id)model{ self.dataAry = model; self.flowLayout.dataArry = model; [self reloadData]; //[tableView reload] is asynchronous, [UICollectionView reloadData] didn't actually finish loading all of the item. / / so collectionViewLayout collectionViewContentSize are inaccurate / / so it needs to be rewritten collectionViewLayout CGFloat updateHeight = self. CollectionViewLayout. CollectionViewContentSize. Height; // Update the height constraint with the newly expanded ContentSize. self.pictureCollectionViewHeightCons.constant = updateHeight; self.pictureCollectionViewWidthCons.constant = self.contentWidth; }Copy the code

The updateHeight returned here is computed by overriding the method, while self.contentWidth is computed by [UIScreen mainScreen].bounds.size. Width, Because UITableViewCell passes layoutIfNeeded to determine the true width of UICollectionView, it only applies to cases where the width of UICollectionView is fixed.

The following

The method is called by assigning to the UITableViewCell

- (CGFloat)calculateRowHeightWithId:(id)model{ self.dataModel = model; if (self.dataModel.height ! = 0) { return self.dataModel.height; } [self em_displayWithID:self.dataModel]; [self layoutIfNeeded]; self.dataModel.height = CGRectGetMaxY(self.stackView.frame); return CGRectGetMaxY(self.stackView.frame); } - (void)em_displayWithID:(id)model{ self.dataModel = model; / *. Omit method. * / [self. TagsCollectionView em_displayWithID: self. The dataModel. TagArry]; }Copy the code

Call the UICollectionView assignment method in the em_displayWithID: method of the UITableViewCell by calling the calculateRowHeightWithId of the UITableViewCell’s calculation height method.

Finally, here is what the effect picture looks like: