2013-05-14 3 views
37

У меня есть UICollectionView со случайными ячейками. Есть ли способ, который позволяет мне центрировать строки?Как я могу центрировать строки в UICollectionView?

Вот как это выглядит по умолчанию:

[ x x x x x x ] 
[ x x x x x x ] 
[ x x   ] 

Вот что желаемое расположение выглядит следующим образом:

[ x x x x x ] 
[ x x x x x ] 
[ x x ] 

ответ

23

Немного фоне первых - это UICollectionView сочетается с UICollectionViewLayout, который определяет как ячейки размещаются в представлении. Это означает, что представление коллекции очень гибкое (вы можете создать с ним практически любой макет), но также означает, что модификация макетов может быть немного запутанной.

Создание совершенно нового класса макета является сложным, поэтому вместо этого вы хотите попробовать изменить макет по умолчанию (UICollectionViewFlowLayout), чтобы получить выравнивание по центру. Чтобы сделать его еще проще, вы, вероятно, хотите избежать подклассификации самой компоновки потока.

Вот один подход (он не может быть лучшим подходом, но это первый один я могу думать) - разделить ваши клетки на две части, а именно:

[ x x x x x ] <-- Section 1 
[ x x x x x ] <-- Section 1 
[ x x ] <-- Section 2 

Это должно быть достаточно простым, если вы знаете ширину прокрутки и количество ячеек, которые могут вставляться в каждую строку.

Затем используйте метод делегата collectionView:layout:insetForSectionAtIndex:, чтобы установить поля для второй секции так, чтобы она выглядела вертикально по центру. Как только вы это сделаете, вам просто нужно убедиться, что вы перекомпилируете соответствующие разделы/вставки разделов, чтобы можно было поддерживать как портретную, так и ландшафтную ориентации.

Существует несколько схожий вопрос здесь - How to center align the cells of a UICollectionView? - это более подробное описание методов вставки, хотя это не совсем попытка сделать то же самое, что и вы.

+0

Ну, я не знаю, это лучший способ сделать это, но он работает. И это все, что мне нужно. Спасибо. – deycall

+0

Я думаю, что это, наверное, самый простой, с точки зрения количества строк/не подклассифицируя макет. Если вы закончите работу над макетом, вы можете рассмотреть этот подход. – lxt

+0

Вы также можете подклассифицировать 'UICollectionViewFlowLayout' вместо' UICollectionViewLayout'. Макет по-прежнему работает на основе незначительных изменений, и это делает большую часть тяжелой работы для вас, вы просто настраиваете бит, который вы хотите. – Fogmeister

44

Я должен был сделать что-то подобное, но мне нужны все ячейки в одном разделе. Было довольно просто увеличить размер UICollectionViewFlowLayout до центральных ячеек. Я сделал стручок:

https://github.com/keighl/KTCenterFlowLayout

enter image description here

+1

Чтобы заставить KTCenterFlowLayout работать, я должен был явно задать размер ячейки в коде, хотя он уже был установлен через раскадровку: 'let layout = KTCenterFlowLayout(); layout.itemSize = CGSizeMake (85, 85) ' – David

+0

Эта штука классная! Благодаря! – Andy

+1

Вы также можете просто установить это как макет своего вида коллекции в построителе интерфейса. Спасибо, Кайл, это здорово! –

2

В случае, если кто имеет CollectionView, который имеет 2 колонки, и если число элементов нечетное, последний пункт должен быть выровнен по центру. Затем используйте этот

DNLastItemCenteredLayout

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect]; 

    for (UICollectionViewLayoutAttributes *attribute in attributes) { 
     NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView 
                 numberOfItemsInSection:attribute.indexPath.section]; 
     if (itemCount % 2 == 1 && attribute.indexPath.item == itemCount - 1) { 
      CGRect originalFrame = attribute.frame; 
      attribute.frame = CGRectMake(self.collectionView.bounds.size.width/2-originalFrame.size.width/2, 
             originalFrame.origin.y, 
             originalFrame.size.width, 
             originalFrame.size.height); 
     } 
    } 

    return attributes; 
} 
1

Просто связать этот макет потока. Вы также можете выровнять центр, влево, вправо.

// 
    // CellAllignmentFlowLayout.swift 
    // UICollectionView 
    // 
    // Created by rajeshkumar Lingavel on 8/11/15. 
    // Copyright © 2015 rajeshkumar Lingavel. All rights reserved. 
    // 

import UIKit 
enum SZAlignment:Int { 
    case Center, 
     left, 
     Right 
} 
class CellAllignmentFlowLayout: UICollectionViewFlowLayout { 
    var alignment:SZAlignment! 
    var padding:CGFloat! 
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 
//  NSArray *allAttributesInRect = [super 
//   layoutAttributesForElementsInRect:rect]; 

     let allAttributesInRect:NSArray = super.layoutAttributesForElementsInRect(rect)! 
     var changedAttributes:NSArray = NSArray() 
     switch(alignment.rawValue){ 
      case 0: 
       changedAttributes = alignCenter(allAttributesInRect) 
      case 1: 
       changedAttributes = alignLeft(allAttributesInRect) 
      case 2: 
       changedAttributes = alignRight(allAttributesInRect) 
      default: 
       assertionFailure("No Direction") 
     } 

     return changedAttributes as? [UICollectionViewLayoutAttributes] 
    } 


    private func alignCenter(allAttributesInRect:NSArray) -> NSArray{ 


     let numberOfSection:Int = (self.collectionView?.numberOfSections())! 

     let redefiendArray = NSMutableArray() 

     for i in 0 ..< numberOfSection { 

       let thisSectionObjects = sectionObjects(allAttributesInRect, section: i) 
       let totalLines = numberOfLines(thisSectionObjects, section: i) 
       let lastrowObjects = lastRow(thisSectionObjects, numberOfRows: totalLines, section: i) 
       let lastRowObjectsRow = setMiddleTheLastRow(lastrowObjects) 
       let start = (thisSectionObjects.count - lastrowObjects.count) 

       for j in start..<thisSectionObjects.count{ 
        thisSectionObjects.replaceObjectAtIndex(j, withObject: lastRowObjectsRow.objectAtIndex(j - start)) 
       } 
      redefiendArray.addObjectsFromArray(thisSectionObjects as [AnyObject]) 
     } 




     return redefiendArray 
    } 
    private func alignLeft(allAttributesInRect:NSArray) -> NSArray{ 


     return allAttributesInRect; 

    } 
    private func alignRight(allAttributesInRect:NSArray) -> NSArray{ 
     return allAttributesInRect; 

    } 

    private func getTotalLenthOftheSection(section:Int,allAttributesInRect:NSArray) -> CGFloat{ 

     var totalLength:CGFloat = 0.0 
     totalLength = totalLength + (CGFloat (((self.collectionView?.numberOfItemsInSection(section))! - 1)) * padding) 
     for attributes in allAttributesInRect { 

      if(attributes.indexPath.section == section){ 
       totalLength = totalLength + attributes.frame.width 
      } 
     } 

     return totalLength 
    } 

    private func numberOfLines(allAttributesInRect:NSArray,section:Int)-> Int{ 
     var totalLines:Int = 0 
     for attributes in allAttributesInRect { 
      if(attributes.indexPath.section == section){ 
       if (attributes.frame.origin.x == self.sectionInset.left){ 
        totalLines = totalLines + 1 
       } 
      } 
     } 
     return totalLines 
    } 
    private func sectionObjects(allAttributesInRect:NSArray,section:Int) -> NSMutableArray{ 
     let objects:NSMutableArray = NSMutableArray() 
     for attributes in allAttributesInRect { 
      if(attributes.indexPath.section == section){ 
       objects.addObject(attributes) 
      } 
     } 
     return objects 
    } 

    private func lastRow(allAttributesInRect:NSArray,numberOfRows:Int,section:Int) -> NSMutableArray{ 
     var totalLines:Int = 0 
     let lastRowArrays:NSMutableArray = NSMutableArray() 
     for attributes in allAttributesInRect { 
      if(attributes.indexPath.section == section){ 
       if (attributes.frame.origin.x == self.sectionInset.left){ 
        totalLines = totalLines + 1 
        if(totalLines == numberOfRows){ 
         lastRowArrays.addObject(attributes) 
        } 
       } 
       else{ 
        if(totalLines == numberOfRows){ 
         lastRowArrays.addObject(attributes) 
        } 
       } 
      } 
     } 
     return lastRowArrays 
    } 
    private func setMiddleTheLastRow(lastRowAttrs:NSMutableArray)->NSMutableArray{ 
     let redefinedValues = NSMutableArray() 
     let totalLengthOftheView = self.collectionView?.frame.width 
     var totalLenthOftheCells:CGFloat = 0.0 
     totalLenthOftheCells = totalLenthOftheCells + (CGFloat (lastRowAttrs.count) - 1) * padding 

     for attrs in lastRowAttrs{ 
      totalLenthOftheCells = totalLenthOftheCells + attrs.frame.width 
     } 

     var initalValue = (totalLengthOftheView!/2) - (totalLenthOftheCells/2) 

     for i in 0..<lastRowAttrs.count { 
      let changeingAttribute:UICollectionViewLayoutAttributes = lastRowAttrs[i] as! UICollectionViewLayoutAttributes 
      var frame = changeingAttribute.frame 
      frame.origin.x = initalValue 
      changeingAttribute.frame = frame 
      redefinedValues.addObject(changeingAttribute) 
      initalValue = initalValue + changeingAttribute.frame.width + padding 
     } 

     return redefinedValues; 
    } 


} 
2

Это может быть достигнуто с помощью (относительно) простой пользовательский макет, наследнике UICollectionViewFlowLayout. Вот пример в Swift:

/** 
* A simple `UICollectionViewFlowLayout` subclass that would make sure the items are center-aligned in the collection view, when scrolling vertically. 
*/ 
class UICollectionViewFlowCenterLayout: UICollectionViewFlowLayout { 

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 
     guard let suggestedAttributes = super.layoutAttributesForElementsInRect(rect) else { return nil } 

     guard scrollDirection == .Vertical else { return suggestedAttributes } 

     var newAttributes: [UICollectionViewLayoutAttributes] = [] 

     /// We will collect items for each row in this array 
     var currentRowAttributes: [UICollectionViewLayoutAttributes] = [] 
     /// We will use this variable to detect new rows when iterating over items 
     var yOffset:CGFloat = sectionInset.top 
     for attributes in suggestedAttributes { 
      /// If we happen to run into a new row... 
      if attributes.frame.origin.y != yOffset { 
       /* 
       * Update layout of all items in the previous row and add them to the resulting array 
       */ 
       centerSingleRowWithItemsAttributes(&currentRowAttributes, rect: rect) 
       newAttributes += currentRowAttributes 
       /* 
       * Reset the accumulated values for the new row 
       */ 
       currentRowAttributes = [] 
       yOffset = attributes.frame.origin.y 
      } 
      currentRowAttributes += [attributes] 
     } 
     /* 
     * Update the layout of the last row. 
     */ 
     centerSingleRowWithItemsAttributes(&currentRowAttributes, rect: rect) 
     newAttributes += currentRowAttributes 

     return newAttributes 
    } 

    /** 
    Updates the attributes for items, so that they are center-aligned in the given rect. 

    - parameter attributes: Attributes of the items 
    - parameter rect:  Bounding rect 
    */ 
    private func centerSingleRowWithItemsAttributes(inout attributes: [UICollectionViewLayoutAttributes], rect: CGRect) { 
     guard let item = attributes.last else { return } 

     let itemsCount = CGFloat(attributes.count) 
     let sideInsets = rect.width - (item.frame.width * itemsCount) - (minimumInteritemSpacing * (itemsCount - 1)) 
     var leftOffset = sideInsets/2 

     for attribute in attributes { 
      attribute.frame.origin.x = leftOffset 
      leftOffset += attribute.frame.width + minimumInteritemSpacing 
     } 
    } 
} 
2

Я подклассы UICollectionViewFlowLayout - изменен код я нашел here для левой выровнен зрения сбора.

  1. выравнивание по левому краю Представление коллекции
  2. Группа атрибутов для линейных массивов
  3. Для каждой строки:
    • рассчитать пространство на правой стороне
    • добавить половину пространства для каждого атрибут линии

Это выглядит следующим образом:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 

    NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect]; 
    NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count]; 

    CGFloat leftMargin = self.sectionInset.left; 
    NSMutableArray *lines = [NSMutableArray array]; 
    NSMutableArray *currLine = [NSMutableArray array]; 

    for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) { 
     // Handle new line 
     BOOL newLine = attributes.frame.origin.x <= leftMargin; 
     if (newLine) { 
      leftMargin = self.sectionInset.left; //will add outside loop 
      currLine = [NSMutableArray arrayWithObject:attributes]; 
     } else { 
      [currLine addObject:attributes]; 
     } 

     if ([lines indexOfObject:currLine] == NSNotFound) { 
      [lines addObject:currLine]; 
     } 

     // Align to the left 
     CGRect newLeftAlignedFrame = attributes.frame; 
     newLeftAlignedFrame.origin.x = leftMargin; 
     attributes.frame = newLeftAlignedFrame; 

     leftMargin += attributes.frame.size.width + self.minimumInteritemSpacing; 
     [newAttributesForElementsInRect addObject:attributes]; 
    } 

    // Center left aligned lines 
    for (NSArray *line in lines) { 
     UICollectionViewLayoutAttributes *lastAttributes = line.lastObject; 
     CGFloat space = CGRectGetWidth(self.collectionView.frame) - CGRectGetMaxX(lastAttributes.frame); 

     for (UICollectionViewLayoutAttributes *attributes in line) { 
      CGRect newFrame = attributes.frame; 
      newFrame.origin.x = newFrame.origin.x + space/2; 
      attributes.frame = newFrame; 

     } 
    } 

    return newAttributesForElementsInRect; 
} 

Надеется, что это помогает кто-то :)

1

Swift 3.0 версии класса:

class UICollectionViewFlowCenterLayout: UICollectionViewFlowLayout { 
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 
     guard let suggestedAttributes = super.layoutAttributesForElements(in: rect) else { return nil } 

     guard scrollDirection == .vertical else { return suggestedAttributes } 

     var newAttributes: [UICollectionViewLayoutAttributes] = [] 

     var currentRowAttributes: [UICollectionViewLayoutAttributes] = [] 
     var yOffset:CGFloat = sectionInset.top 
     for attributes in suggestedAttributes { 
      if attributes.frame.origin.y != yOffset { 
       centerSingleRowWithItemsAttributes(attributes: &currentRowAttributes, rect: rect) 
       newAttributes += currentRowAttributes 
       currentRowAttributes = [] 
       yOffset = attributes.frame.origin.y 
      } 
      currentRowAttributes += [attributes] 
     } 
     centerSingleRowWithItemsAttributes(attributes: &currentRowAttributes, rect: rect) 
     newAttributes += currentRowAttributes 

     return newAttributes 
    } 


    private func centerSingleRowWithItemsAttributes(attributes: inout [UICollectionViewLayoutAttributes], rect: CGRect) { 
     guard let item = attributes.last else { return } 

     let itemsCount = CGFloat(attributes.count) 
     let sideInsets = rect.width - (item.frame.width * itemsCount) - (minimumInteritemSpacing * (itemsCount - 1)) 
     var leftOffset = sideInsets/2 

     for attribute in attributes { 
      attribute.frame.origin.x = leftOffset 
      leftOffset += attribute.frame.width + minimumInteritemSpacing 
     } 
    } 
} 
+0

Привет, как я могу использовать этот класс. im новое для xcode. – MRizwan33

0

я сделал Swift 4 версии Kyle Траскотт answer:

import UIKit 

class CenterFlowLayout: UICollectionViewFlowLayout { 

    private var attrCache = [IndexPath: UICollectionViewLayoutAttributes]() 

    override func prepare() { 
     attrCache = [:] 
    } 

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 
     var updatedAttributes = [UICollectionViewLayoutAttributes]() 

     let sections = self.collectionView?.numberOfSections ?? 0 
     var section = 0 
     while section < sections { 
      let items = self.collectionView?.numberOfItems(inSection: section) ?? 0 
      var item = 0 
      while item < items { 
       let indexPath = IndexPath(row: item, section: section) 

       if let attributes = layoutAttributesForItem(at: indexPath), attributes.frame.intersects(rect) { 
        updatedAttributes.append(attributes) 
       } 

       let headerKind = UICollectionElementKindSectionHeader 
       if let headerAttributes = layoutAttributesForSupplementaryView(ofKind: headerKind, at: indexPath) { 
        updatedAttributes.append(headerAttributes) 
       } 

       let footerKind = UICollectionElementKindSectionFooter 
       if let footerAttributes = layoutAttributesForSupplementaryView(ofKind: footerKind, at: indexPath) { 
        updatedAttributes.append(footerAttributes) 
       } 

       item += 1 
      } 

      section += 1 
     } 

     return updatedAttributes 
    } 

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 
     if let attributes = attrCache[indexPath] { 
      return attributes 
     } 

     // Find the other items in the same "row" 
     var rowBuddies = [UICollectionViewLayoutAttributes]() 

     // Calculate the available width to center stuff within 
     // sectionInset is NOT applicable here because a) we're centering stuff 
     // and b) Flow layout has arranged the cells to respect the inset. We're 
     // just hijacking the X position. 
     var collectionViewWidth: CGFloat = 0 
     if let collectionView = collectionView { 
      collectionViewWidth = collectionView.bounds.width - collectionView.contentInset.left 
        - collectionView.contentInset.right 
     } 

     // To find other items in the "row", we need a rect to check intersects against. 
     // Take the item attributes frame (from vanilla flow layout), and stretch it out 
     var rowTestFrame: CGRect = super.layoutAttributesForItem(at: indexPath)?.frame ?? .zero 
     rowTestFrame.origin.x = 0 
     rowTestFrame.size.width = collectionViewWidth 

     let totalRows = self.collectionView?.numberOfItems(inSection: indexPath.section) ?? 0 

     // From this item, work backwards to find the first item in the row 
     // Decrement the row index until a) we get to 0, b) we reach a previous row 
     var rowStartIDX = indexPath.row 
     while true { 
      let prevIDX = rowStartIDX - 1 

      if prevIDX < 0 { 
       break 
      } 

      let prevPath = IndexPath(row: prevIDX, section: indexPath.section) 
      let prevFrame: CGRect = super.layoutAttributesForItem(at: prevPath)?.frame ?? .zero 

      // If the item intersects the test frame, it's in the same row 
      if prevFrame.intersects(rowTestFrame) { 
       rowStartIDX = prevIDX 
      } else { 
       // Found previous row, escape! 
       break 
      } 
     } 

     // Now, work back UP to find the last item in the row 
     // For each item in the row, add it's attributes to rowBuddies 
     var buddyIDX = rowStartIDX 
     while true { 
      if buddyIDX > totalRows - 1 { 
       break 
      } 

      let buddyPath = IndexPath(row: buddyIDX, section: indexPath.section) 

      if let buddyAttributes = super.layoutAttributesForItem(at: buddyPath), 
       buddyAttributes.frame.intersects(rowTestFrame), 
       let buddyAttributesCopy = buddyAttributes.copy() as? UICollectionViewLayoutAttributes { 
       // If the item intersects the test frame, it's in the same row 
       rowBuddies.append(buddyAttributesCopy) 
       buddyIDX += 1 
      } else { 
       // Encountered next row 
       break 
      } 
     } 

     let flowDelegate = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout 
     let selector = #selector(UICollectionViewDelegateFlowLayout.collectionView(_:layout:minimumInteritemSpacingForSectionAt:)) 
     let delegateSupportsInteritemSpacing = flowDelegate?.responds(to: selector) ?? false 

     // x-x-x-x ... sum up the interim space 
     var interitemSpacing = minimumInteritemSpacing 

     // Check for minimumInteritemSpacingForSectionAtIndex support 
     if let collectionView = collectionView, delegateSupportsInteritemSpacing && rowBuddies.count > 0 { 
      interitemSpacing = flowDelegate?.collectionView?(collectionView, 
        layout: self, 
        minimumInteritemSpacingForSectionAt: indexPath.section) ?? 0 
     } 

     let aggregateInteritemSpacing = interitemSpacing * CGFloat(rowBuddies.count - 1) 

     // Sum the width of all elements in the row 
     var aggregateItemWidths: CGFloat = 0 
     for itemAttributes in rowBuddies { 
      aggregateItemWidths += itemAttributes.frame.width 
     } 

     // Build an alignment rect 
     // | |x-x-x-x| | 
     let alignmentWidth = aggregateItemWidths + aggregateInteritemSpacing 
     let alignmentXOffset: CGFloat = (collectionViewWidth - alignmentWidth)/2 

     // Adjust each item's position to be centered 
     var previousFrame: CGRect = .zero 
     for itemAttributes in rowBuddies { 
      var itemFrame = itemAttributes.frame 

      if previousFrame.equalTo(.zero) { 
       itemFrame.origin.x = alignmentXOffset 
      } else { 
       itemFrame.origin.x = previousFrame.maxX + interitemSpacing 
      } 

      itemAttributes.frame = itemFrame 
      previousFrame = itemFrame 

      // Finally, add it to the cache 
      attrCache[itemAttributes.indexPath] = itemAttributes 
     } 

     return attrCache[indexPath] 
    } 
} 
Смежные вопросы