当前位置:  开发笔记 > IOS > 正文

iOS UICollectionView:具有交替网格对齐的圆形视图的单元格

如何解决《iOSUICollectionView:具有交替网格对齐的圆形视图的单元格》经验,为你挑选了1个好方法。

我正在尝试以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:),即,特定细胞的关键属性(即centersize)是什么; 和

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相应地获得所需的布局.



1> Rob..:

基本思想是创建一个UICollectionViewLayout实现的自定义:

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

layoutAttributesForItem(at indexPath:),即,特定细胞的关键属性(即centersize)是什么; 和

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相应地获得所需的布局.

推荐阅读
pan2502851807
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有