iOS UICollectionView:具有交替网格对齐的圆形视图的单元格 [英] iOS UICollectionView: Cells with circular view in alternating grid alignment

查看:99
本文介绍了iOS UICollectionView:具有交替网格对齐的圆形视图的单元格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为圆形的自定义单元格实现 UICollectionView 。现在默认情况下,圆圈的对齐方式与普通方形单元格相同:顶部圆圈和底部圆圈位于同一垂直线上。如何更改对齐方式:顶部圆圈和下面的两个圆圈形成等边三角形(顶部圆圈和底部圆圈的位置偏移半径'长度)?如下:

 来自OOO 
OOO
OOO

to OOO
OOO(圆圈之间没有间距)
OOO


解决方案

基本思路是创建一个自定义 UICollectionViewLayout ,它实现:




  • collectionViewContentSize ,即集合视图的完整,可滚动的 contentSize 的大小是多少;


  • layoutAttributesForItem(在indexPath :) ,即什么是关键属性(即<$ c $特定单元格的c> center 和 size );和


  • layoutAttributesForElements(在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!

覆盖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(上:10,左:多余/ 2,下:10,右:多余/ 2)
}

覆盖va r collectionViewContentSize:CGSize {
返回CGSize(width:collectionView!.bounds.width,
height:2 * radiusOfCircleViews + CGFloat(rows - 1)* circleViewCenterOffset.y + insets.top + insets.bottom)
}

覆盖func layoutAttributesForItem(在indexPath:IndexPath处) - > UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWith:indexPath)

attributes.center = centerForItem(at:indexPath)
attributes.size = itemSize

return属性
}

覆盖func layoutAttributesForElements(在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`在Xcode版本之前9.3
}

private func centerForItem(在indexPath) :IndexPath) - > CGPoint {
let row = indexPath.item / itemsPerRow
let col = indexPath.item - row * itemsPerRow

var x:CGFloat = radiusOfCircleViews + CGFloat(col)*(radiusOfCircleViews * 2)
让y:CGFloat = radiusOfCircleViews + CGFloat(row)*(circleViewCenterOffset.y)

如果行%2 == 0 {
x + = circleViewCenterOffset.x
}

返回CGPoint(x:x + insets.left,y:y + insets.top)
}

private func rectForItem(在indexPath处) :IndexPath) - > CGRect {
let center = centerForItem(at:indexPath)

返回CGRect(x:center.x - radiusOfCircleViews,y:center.y - radiusOfCircleViews,width:radiusOfCircleViews * 2,height: radiusOfCircleViews * 2)
}
}

产生:





显然,根据需要自定义此项,但它说明了基本的想法。






在我的原始答案中,我假设你想看到这些细胞在一个圆圈中,如同在WWDC 2012视频中显示






请参阅 https:/ /github.com/robertmryan/CircularCollectionView 示例。






注意,你提到你想要圈子之间没有间距,所以只需调整 radius 和/或 itemSize 相应地获得你想要的布局。


I'm trying to implement the UICollectionView for custom cells in the shape of circle. Now by default the circles are aligned in the same way as the normal square cell: top circle and bottom circle are on the same vertical line. How can I change the alignment to this: the top circle and two circles below it form an equilateral triangle (positions of top circle and bottom circle are shifted by radius' length)? As below:

from OOO
     OOO
     OOO

to   O O O
    O O O (no spacing among the circles)
     O O O

解决方案

The basic idea is to create a custom UICollectionViewLayout that implements:

  • collectionViewContentSize, i.e., what's the size of the full, scrollable contentSize of the collection view;

  • layoutAttributesForItem(at indexPath:), i.e., what are the key attributes (namely center and size) of a particular cell; and

  • layoutAttributesForElements(in rect:), i.e., what are the key attributes for cells that fall within this particular rect ... this will be used to identify which cells are visible at any given point in time as well as the attributes for those cells; this is basically an array of the attributes for the cells from the previous method, filtered down to just the ones within that rect.

Thus, in Swift 3 you could do something like:

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)
    }        
}

That yields:

Clearly, customize this as you see fit, but it illustrates the basic idea.


In my original answer, below, I assumed you wanted to see these cells in a circle, as shown in WWDC 2012 video Advanced Collection Views and Building Custom Layouts (about 40+ minutes into the video). See that below.


For example, in 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))
        }
    }
}

Then you can simply set the collectionViewLayout and then implement the standard UICollectionViewDataSource methods.

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
    }
}

That yields:


See https://github.com/robertmryan/CircularCollectionView for sample.


Note, you mentioned that you want "no spacing among the circles", so just adjust the radius and/or itemSize accordingly to get the layout you want.

这篇关于iOS UICollectionView:具有交替网格对齐的圆形视图的单元格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆