Left Align Cells in UICollectionView - Printable Version +- 0Day Forums (https://0day.red) +-- Forum: Coding (https://0day.red/Forum-Coding) +--- Forum: Objective-C (https://0day.red/Forum-Objective-C) +--- Thread: Left Align Cells in UICollectionView (/Thread-Left-Align-Cells-in-UICollectionView) |
Left Align Cells in UICollectionView - liesbethteajrjmta - 07-21-2023 I am using a UICollectionView in my project, where there are multiple cells of differing widths on a line. According to: [To see links please register here] it spreads the cells out across the line with equal padding. This happens as expected, except I want to left justify them, and hard code a padding width. I figure I need to subclass UICollectionViewFlowLayout, however after reading some of the tutorials etc online I just don't seem to get how this works. RE: Left Align Cells in UICollectionView - Darryl288902 - 07-21-2023 The problem with UICollectionView is that it tries to automatically fit the cells in the available area. I have done this by first defining number of rows and columns and then defining the cell size for that row and column 1) To define Sections (Rows) of my UICollectionView: (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 2) To define number of items in a section. You can define different number of items for every section. you can get section number using 'section' parameter. (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 3) To define Cell size for each section and row separately. You can get section number and row number using the 'indexPath' parameter i.e. **`[indexPath section]`** for section number and **`[indexPath row]`** for row number. (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 4) Then you can display your cells in rows and sections using: (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath NOTE: In UICollectionView Section == Row IndexPath.Row == Column RE: Left Align Cells in UICollectionView - daveene - 07-21-2023 Building on [Michael Sand's answer][1], I created a subclassed `UICollectionViewFlowLayout` library to do left, right, or full (basically the default) horizontal justification—it also lets you set the absolute distance between each cell. I plan on adding horizontal center justification and vertical justification to it, too. [To see links please register here] [1]: [To see links please register here] RE: Left Align Cells in UICollectionView - dottyjilgupe - 07-21-2023 I had the same problem, Give the Cocoapod [UICollectionViewLeftAlignedLayout][1] a try. Just include it in your project and initialize it like this: UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init]; UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout]; [1]: [To see links please register here] RE: Left Align Cells in UICollectionView - barrenness35801 - 07-21-2023 Thanks for the [Michael Sand's answer]( [To see links please register here] ). I modified it to a solution of multiple rows (the same alignment Top y of each row) that is Left Alignment, even spacing to each item.static CGFloat const ITEM_SPACING = 10.0f; - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { CGRect contentRect = {CGPointZero, self.collectionViewContentSize}; NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:contentRect]; NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count]; CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use. NSMutableDictionary *leftMarginDictionary = [[NSMutableDictionary alloc] init]; for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) { UICollectionViewLayoutAttributes *attr = attributes.copy; CGFloat lastLeftMargin = [[leftMarginDictionary valueForKey:[[NSNumber numberWithFloat:attributes.frame.origin.y] stringValue]] floatValue]; if (lastLeftMargin == 0) lastLeftMargin = leftMargin; CGRect newLeftAlignedFrame = attr.frame; newLeftAlignedFrame.origin.x = lastLeftMargin; attr.frame = newLeftAlignedFrame; lastLeftMargin += attr.frame.size.width + ITEM_SPACING; [leftMarginDictionary setObject:@(lastLeftMargin) forKey:[[NSNumber numberWithFloat:attributes.frame.origin.y] stringValue]]; [newAttributesForElementsInRect addObject:attr]; } return newAttributesForElementsInRect; } RE: Left Align Cells in UICollectionView - llanes94 - 07-21-2023 Mike Sand's answer is good but i had experienced some issues with this code (Like lengthy cell clipped out). And new code: #define ITEM_SPACE 7.0f @implementation LeftAlignedCollectionViewFlowLayout - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect]; for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) { if (nil == attributes.representedElementKind) { NSIndexPath* indexPath = attributes.indexPath; attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame; } } return attributesToReturn; } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath]; UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset]; if (indexPath.item == 0) { // first item of section CGRect frame = currentItemAttributes.frame; frame.origin.x = sectionInset.left; // first item of the section should always be left aligned currentItemAttributes.frame = frame; return currentItemAttributes; } NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section]; CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame; CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width + ITEM_SPACE; CGRect currentFrame = currentItemAttributes.frame; CGRect strecthedCurrentFrame = CGRectMake(0, currentFrame.origin.y, self.collectionView.frame.size.width, currentFrame.size.height); if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line // the approach here is to take the current frame, left align it to the edge of the view // then stretch it the width of the collection view, if it intersects with the previous frame then that means it // is on the same line, otherwise it is on it's own new line CGRect frame = currentItemAttributes.frame; frame.origin.x = sectionInset.left; // first item on the line should always be left aligned currentItemAttributes.frame = frame; return currentItemAttributes; } CGRect frame = currentItemAttributes.frame; frame.origin.x = previousFrameRightPoint; currentItemAttributes.frame = frame; return currentItemAttributes; } RE: Left Align Cells in UICollectionView - esophagus299282 - 07-21-2023 The question has been up a while but there's no answer and it's a good question. The answer is to override one method in the UICollectionViewFlowLayout subclass: @implementation MYFlowLayoutSubclass //Note, the layout's minimumInteritemSpacing (default 10.0) should not be less than this. #define ITEM_SPACING 10.0f - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect]; NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count]; CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use. //this loop assumes attributes are in IndexPath order for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) { if (attributes.frame.origin.x == self.sectionInset.left) { leftMargin = self.sectionInset.left; //will add outside loop } else { CGRect newLeftAlignedFrame = attributes.frame; newLeftAlignedFrame.origin.x = leftMargin; attributes.frame = newLeftAlignedFrame; } leftMargin += attributes.frame.size.width + ITEM_SPACING; [newAttributesForElementsInRect addObject:attributes]; } return newAttributesForElementsInRect; } @end As recommended by Apple, you get the layout attributes from super and iterate over them. If it's the first in the row (defined by its origin.x being on the left margin), you leave it alone and reset the x to zero. Then for the first cell and every cell, you add the width of that cell plus some margin. This gets passed to the next item in the loop. If it's not the first item, you set it's origin.x to the running calculated margin, and add new elements to the array. RE: Left Align Cells in UICollectionView - quarterdeck99 - 07-21-2023 The above code works for me. I would like to share the respective Swift 3.0 code. class SFFlowLayout: UICollectionViewFlowLayout { let itemSpacing: CGFloat = 3.0 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attriuteElementsInRect = super.layoutAttributesForElements(in: rect) var newAttributeForElement: Array<UICollectionViewLayoutAttributes> = [] var leftMargin = self.sectionInset.left for tempAttribute in attriuteElementsInRect! { let attribute = tempAttribute if attribute.frame.origin.x == self.sectionInset.left { leftMargin = self.sectionInset.left } else { var newLeftAlignedFrame = attribute.frame newLeftAlignedFrame.origin.x = leftMargin attribute.frame = newLeftAlignedFrame } leftMargin += attribute.frame.size.width + itemSpacing newAttributeForElement.append(attribute) } return newAttributeForElement } } RE: Left Align Cells in UICollectionView - crocanthemum437357 - 07-21-2023 With Swift 4.1 and iOS 11, according to your needs, you may choose one of the **2 following complete implementations** in order to fix your problem. --- ## #1. Left align autoresizing `UICollectionViewCell`s The implementation below shows how to use `UICollectionViewLayout`'s [`layoutAttributesForElements(in:)`][1], `UICollectionViewFlowLayout`'s [`estimatedItemSize`][2] and `UILabel`'s [`preferredMaxLayoutWidth`][3] in order to left align autoresizing cells in a `UICollectionView`: *CollectionViewController.swift* import UIKit class CollectionViewController: UICollectionViewController { let array = ["1", "1 2", "1 2 3 4 5 6 7 8", "1 2 3 4 5 6 7 8 9 10 11", "1 2 3", "1 2 3 4", "1 2 3 4 5 6", "1 2 3 4 5 6 7 8 9 10", "1 2 3 4", "1 2 3 4 5 6 7", "1 2 3 4 5 6 7 8 9", "1", "1 2 3 4 5", "1", "1 2 3 4 5 6"] let columnLayout = FlowLayout( minimumInteritemSpacing: 10, minimumLineSpacing: 10, sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) ) override func viewDidLoad() { super.viewDidLoad() collectionView?.collectionViewLayout = columnLayout collectionView?.contentInsetAdjustmentBehavior = .always collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell") } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return array.count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell cell.label.text = array[indexPath.row] return cell } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { collectionView?.collectionViewLayout.invalidateLayout() super.viewWillTransition(to: size, with: coordinator) } } *FlowLayout.swift* import UIKit class FlowLayout: UICollectionViewFlowLayout { required init(minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) { super.init() estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize self.minimumInteritemSpacing = minimumInteritemSpacing self.minimumLineSpacing = minimumLineSpacing self.sectionInset = sectionInset sectionInsetReference = .fromSafeArea } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes } guard scrollDirection == .vertical else { return layoutAttributes } // Filter attributes to compute only cell attributes let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell }) // Group cell attributes by row (cells with same vertical center) and loop on those groups for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) { // Set the initial left inset var leftInset = sectionInset.left // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell for attribute in attributes { attribute.frame.origin.x = leftInset leftInset = attribute.frame.maxX + minimumInteritemSpacing } } return layoutAttributes } } *CollectionViewCell.swift* import UIKit class CollectionViewCell: UICollectionViewCell { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .orange label.preferredMaxLayoutWidth = 120 label.numberOfLines = 0 contentView.addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false contentView.layoutMarginsGuide.topAnchor.constraint(equalTo: label.topAnchor).isActive = true contentView.layoutMarginsGuide.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } Expected result: [![enter image description here][4]][4] --- ## #2. Left align `UICollectionViewCell`s with fixed size The implementation below shows how to use `UICollectionViewLayout`'s [`layoutAttributesForElements(in:)`][1] and `UICollectionViewFlowLayout`'s [`itemSize`][5] in order to left align cells with predefined size in a `UICollectionView`: *CollectionViewController.swift* import UIKit class CollectionViewController: UICollectionViewController { let columnLayout = FlowLayout( itemSize: CGSize(width: 140, height: 140), minimumInteritemSpacing: 10, minimumLineSpacing: 10, sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) ) override func viewDidLoad() { super.viewDidLoad() collectionView?.collectionViewLayout = columnLayout collectionView?.contentInsetAdjustmentBehavior = .always collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell") } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 7 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell return cell } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { collectionView?.collectionViewLayout.invalidateLayout() super.viewWillTransition(to: size, with: coordinator) } } *FlowLayout.swift* import UIKit class FlowLayout: UICollectionViewFlowLayout { required init(itemSize: CGSize, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) { super.init() self.itemSize = itemSize self.minimumInteritemSpacing = minimumInteritemSpacing self.minimumLineSpacing = minimumLineSpacing self.sectionInset = sectionInset sectionInsetReference = .fromSafeArea } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes } guard scrollDirection == .vertical else { return layoutAttributes } // Filter attributes to compute only cell attributes let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell }) // Group cell attributes by row (cells with same vertical center) and loop on those groups for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) { // Set the initial left inset var leftInset = sectionInset.left // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell for attribute in attributes { attribute.frame.origin.x = leftInset leftInset = attribute.frame.maxX + minimumInteritemSpacing } } return layoutAttributes } } *CollectionViewCell.swift* import UIKit class CollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .cyan } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } Expected result: [![enter image description here][6]][6] [1]: [To see links please register here] [2]:[To see links please register here] [3]:[To see links please register here] [4]:[5]: [To see links please register here] [6]:RE: Left Align Cells in UICollectionView - zulemakypmmxlu - 07-21-2023 If anyone of you facing issue - **some of the cells that's on the right of the collection view exceeding the bounds of the collection view**. Then please use this - ``` class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) var leftMargin : CGFloat = sectionInset.left var maxY: CGFloat = -1.0 attributes?.forEach { layoutAttribute in if Int(layoutAttribute.frame.origin.y) >= Int(maxY) { leftMargin = sectionInset.left } layoutAttribute.frame.origin.x = leftMargin leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing maxY = max(layoutAttribute.frame.maxY , maxY) } return attributes } } ``` Use **INT** in place of comparing **CGFloat** values. |