我正在尝试以UICollectionView
圆形形状实现自定义单元格.现在默认情况下,圆圈的对齐方式与普通方形单元格相同:顶部圆圈和底部圆圈位于同一垂直线上.如何更改对齐方式:顶部圆圈和下面的两个圆圈形成等边三角形(顶部圆圈和底部圆圈的位置偏移半径"长度")?如下:
from OOO OOO OOO to O O O O O O (no spacing among the circles) O O O
Rob.. 8
基本思想是创建一个UICollectionViewLayout
实现的自定义:
collectionViewContentSize
,即,contentSize
集合视图的完整,可滚动的大小是多少;
layoutAttributesForItem(at indexPath:)
,即,特定细胞的关键属性(即center
和size
)是什么; 和
layoutAttributesForElements(in rect:)
,即,属于这个特定范围的细胞的关键属性是什么rect
......这将用于识别在任何给定时间点哪些细胞是可见的以及这些细胞的属性; 这基本上是前一个方法中单元格的属性数组,过滤到其中的单元格rect
.
因此,在Swift 3中,您可以执行以下操作:
class AlternatingGridLayout: UICollectionViewLayout { private var itemSize: CGSize! private var numberOfItems: Int! private var itemsPerRow: Int! private var rows: Int! private var circleViewCenterOffset: CGPoint! private var radiusOfCircleViews: CGFloat! private var insets: UIEdgeInsets! override func prepare() { super.prepare() guard let collectionView = collectionView else { return } radiusOfCircleViews = CGFloat(40.0) itemSize = CGSize(width: radiusOfCircleViews * 2, height: radiusOfCircleViews * 2) circleViewCenterOffset = CGPoint(x: 2 * radiusOfCircleViews * cos(.pi / 3), y: 2 * radiusOfCircleViews * sin(.pi / 3)) numberOfItems = collectionView.numberOfItems(inSection: 0) itemsPerRow = Int(floor((collectionView.bounds.width - radiusOfCircleViews) / CGFloat(2 * radiusOfCircleViews)) + 0.5) rows = (numberOfItems - 1) / itemsPerRow + 1 let excess = collectionView.bounds.width - (CGFloat(itemsPerRow) * radiusOfCircleViews * 2 + circleViewCenterOffset.x) insets = UIEdgeInsets(top: 10, left: excess / 2, bottom: 10, right: excess / 2) } override var collectionViewContentSize: CGSize { return CGSize(width: collectionView!.bounds.width, height: 2 * radiusOfCircleViews + CGFloat(rows - 1) * circleViewCenterOffset.y + insets.top + insets.bottom) } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.center = centerForItem(at: indexPath) attributes.size = itemSize return attributes } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return (0 ..< collectionView!.numberOfItems(inSection: 0)).map { IndexPath(item: $0, section: 0) } .filter { rect.intersects(rectForItem(at: $0)) } .compactMap { self.layoutAttributesForItem(at: $0) } // `flatMap` in Xcode versions before 9.3 } private func centerForItem(at indexPath: IndexPath) -> CGPoint { let row = indexPath.item / itemsPerRow let col = indexPath.item - row * itemsPerRow var x: CGFloat = radiusOfCircleViews + CGFloat(col) * (radiusOfCircleViews * 2) let y: CGFloat = radiusOfCircleViews + CGFloat(row) * (circleViewCenterOffset.y) if row % 2 == 0 { x += circleViewCenterOffset.x } return CGPoint(x: x + insets.left, y: y + insets.top) } private func rectForItem(at indexPath: IndexPath) -> CGRect { let center = centerForItem(at: indexPath) return CGRect(x: center.x - radiusOfCircleViews, y: center.y - radiusOfCircleViews, width: radiusOfCircleViews * 2, height: radiusOfCircleViews * 2) } }
产量:
显然,根据您的需要自定义,但它说明了基本的想法.
在下面的原始答案中,我假设您希望将这些单元格放在一个圆圈中,如WWDC 2012视频高级集合视图和构建自定义布局(视频大约40分钟以上)所示.见下文.
例如,在Swift 3中:
class CircleLayout: UICollectionViewLayout { private var center: CGPoint! private var itemSize: CGSize! private var radius: CGFloat! private var numberOfItems: Int! override func prepare() { super.prepare() guard let collectionView = collectionView else { return } center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY) let shortestAxisLength = min(collectionView.bounds.width, collectionView.bounds.height) itemSize = CGSize(width: shortestAxisLength * 0.1, height: shortestAxisLength * 0.1) radius = shortestAxisLength * 0.4 numberOfItems = collectionView.numberOfItems(inSection: 0) } override var collectionViewContentSize: CGSize { return collectionView!.bounds.size } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) let angle = 2 * .pi * CGFloat(indexPath.item) / CGFloat(numberOfItems) attributes.center = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle)) attributes.size = itemSize return attributes } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return (0 ..< collectionView!.numberOfItems(inSection: 0)) .compactMap { item -> UICollectionViewLayoutAttributes? in // `flatMap` in Xcode versions prior to 9.3 self.layoutAttributesForItem(at: IndexPath(item: item, section: 0)) } } }
然后你可以简单地设置collectionViewLayout
然后实现标准UICollectionViewDataSource
方法.
class ViewController: UICollectionViewController { var numberOfCells = 10 override func viewDidLoad() { super.viewDidLoad() collectionView?.collectionViewLayout = CircleLayout() // just for giggles and grins, let's show the insertion of a cell DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.collectionView?.performBatchUpdates({ self.numberOfCells += 1 self.collectionView?.insertItems(at: [IndexPath(item: 0, section: 0)]) }, completion: nil) } } } // MARK: UICollectionViewDataSource extension ViewController { override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return numberOfCells } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CircleCell", for: indexPath) return cell } }
产量:
有关示例,请参阅https://github.com/robertmryan/CircularCollectionView.
请注意,您提到您希望"圆圈之间没有间距",因此只需调整radius
和/或itemSize
相应地获得所需的布局.
基本思想是创建一个UICollectionViewLayout
实现的自定义:
collectionViewContentSize
,即,contentSize
集合视图的完整,可滚动的大小是多少;
layoutAttributesForItem(at indexPath:)
,即,特定细胞的关键属性(即center
和size
)是什么; 和
layoutAttributesForElements(in rect:)
,即,属于这个特定范围的细胞的关键属性是什么rect
......这将用于识别在任何给定时间点哪些细胞是可见的以及这些细胞的属性; 这基本上是前一个方法中单元格的属性数组,过滤到其中的单元格rect
.
因此,在Swift 3中,您可以执行以下操作:
class AlternatingGridLayout: UICollectionViewLayout { private var itemSize: CGSize! private var numberOfItems: Int! private var itemsPerRow: Int! private var rows: Int! private var circleViewCenterOffset: CGPoint! private var radiusOfCircleViews: CGFloat! private var insets: UIEdgeInsets! override func prepare() { super.prepare() guard let collectionView = collectionView else { return } radiusOfCircleViews = CGFloat(40.0) itemSize = CGSize(width: radiusOfCircleViews * 2, height: radiusOfCircleViews * 2) circleViewCenterOffset = CGPoint(x: 2 * radiusOfCircleViews * cos(.pi / 3), y: 2 * radiusOfCircleViews * sin(.pi / 3)) numberOfItems = collectionView.numberOfItems(inSection: 0) itemsPerRow = Int(floor((collectionView.bounds.width - radiusOfCircleViews) / CGFloat(2 * radiusOfCircleViews)) + 0.5) rows = (numberOfItems - 1) / itemsPerRow + 1 let excess = collectionView.bounds.width - (CGFloat(itemsPerRow) * radiusOfCircleViews * 2 + circleViewCenterOffset.x) insets = UIEdgeInsets(top: 10, left: excess / 2, bottom: 10, right: excess / 2) } override var collectionViewContentSize: CGSize { return CGSize(width: collectionView!.bounds.width, height: 2 * radiusOfCircleViews + CGFloat(rows - 1) * circleViewCenterOffset.y + insets.top + insets.bottom) } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.center = centerForItem(at: indexPath) attributes.size = itemSize return attributes } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return (0 ..< collectionView!.numberOfItems(inSection: 0)).map { IndexPath(item: $0, section: 0) } .filter { rect.intersects(rectForItem(at: $0)) } .compactMap { self.layoutAttributesForItem(at: $0) } // `flatMap` in Xcode versions before 9.3 } private func centerForItem(at indexPath: IndexPath) -> CGPoint { let row = indexPath.item / itemsPerRow let col = indexPath.item - row * itemsPerRow var x: CGFloat = radiusOfCircleViews + CGFloat(col) * (radiusOfCircleViews * 2) let y: CGFloat = radiusOfCircleViews + CGFloat(row) * (circleViewCenterOffset.y) if row % 2 == 0 { x += circleViewCenterOffset.x } return CGPoint(x: x + insets.left, y: y + insets.top) } private func rectForItem(at indexPath: IndexPath) -> CGRect { let center = centerForItem(at: indexPath) return CGRect(x: center.x - radiusOfCircleViews, y: center.y - radiusOfCircleViews, width: radiusOfCircleViews * 2, height: radiusOfCircleViews * 2) } }
产量:
显然,根据您的需要自定义,但它说明了基本的想法.
在下面的原始答案中,我假设您希望将这些单元格放在一个圆圈中,如WWDC 2012视频高级集合视图和构建自定义布局(视频大约40分钟以上)所示.见下文.
例如,在Swift 3中:
class CircleLayout: UICollectionViewLayout { private var center: CGPoint! private var itemSize: CGSize! private var radius: CGFloat! private var numberOfItems: Int! override func prepare() { super.prepare() guard let collectionView = collectionView else { return } center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY) let shortestAxisLength = min(collectionView.bounds.width, collectionView.bounds.height) itemSize = CGSize(width: shortestAxisLength * 0.1, height: shortestAxisLength * 0.1) radius = shortestAxisLength * 0.4 numberOfItems = collectionView.numberOfItems(inSection: 0) } override var collectionViewContentSize: CGSize { return collectionView!.bounds.size } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) let angle = 2 * .pi * CGFloat(indexPath.item) / CGFloat(numberOfItems) attributes.center = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle)) attributes.size = itemSize return attributes } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return (0 ..< collectionView!.numberOfItems(inSection: 0)) .compactMap { item -> UICollectionViewLayoutAttributes? in // `flatMap` in Xcode versions prior to 9.3 self.layoutAttributesForItem(at: IndexPath(item: item, section: 0)) } } }
然后你可以简单地设置collectionViewLayout
然后实现标准UICollectionViewDataSource
方法.
class ViewController: UICollectionViewController { var numberOfCells = 10 override func viewDidLoad() { super.viewDidLoad() collectionView?.collectionViewLayout = CircleLayout() // just for giggles and grins, let's show the insertion of a cell DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.collectionView?.performBatchUpdates({ self.numberOfCells += 1 self.collectionView?.insertItems(at: [IndexPath(item: 0, section: 0)]) }, completion: nil) } } } // MARK: UICollectionViewDataSource extension ViewController { override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return numberOfCells } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CircleCell", for: indexPath) return cell } }
产量:
有关示例,请参阅https://github.com/robertmryan/CircularCollectionView.
请注意,您提到您希望"圆圈之间没有间距",因此只需调整radius
和/或itemSize
相应地获得所需的布局.