使用自定义维度重新排序Collection View单元时出现问题

Alessionossa

我想使用每个单元格的自定义大小对集合视图中的单元格进行重新排序。
在“集合视图”的每个单元格中都有一个带有单词的标签。
我使用以下代码设置每个单元的尺寸:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    let word = textArray[indexPath.row]

    let font = UIFont.systemFont(ofSize: 17)
    let fontAttributes = [NSFontAttributeName: font]
    var size = (word as NSString).size(attributes: fontAttributes)
    size.width = size.width + 2
    return size
}

我使用以下代码对集合视图重新排序:

override func viewDidLoad() {
    super.viewDidLoad()

    self.installsStandardGestureForInteractiveMovement = false
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(gesture:)))
    self.collectionView?.addGestureRecognizer(panGesture)

}

func handlePanGesture(gesture: UIPanGestureRecognizer) {
    switch gesture.state {
    case UIGestureRecognizerState.began :
        guard let selectedIndexPath = self.collectionView?.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
            break
        }
        collectionView?.beginInteractiveMovementForItem(at: selectedIndexPath)
        print("Interactive movement began")

    case UIGestureRecognizerState.changed :
        collectionView?.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
        print("Interactive movement changed")

    case UIGestureRecognizerState.ended :
        collectionView?.endInteractiveMovement()
        print("Interactive movement ended")

    default:
        collectionView?.cancelInteractiveMovement()
        print("Interactive movement canceled")
    }
}

override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {

    // Swap values if sorce and destination
    let change = textArray[sourceIndexPath.row]


    textArray.remove(at: sourceIndexPath.row)
    textArray.insert(change, at: destinationIndexPath.row)

    // Reload data to recalculate dimensions for the cells
    collectionView.reloadData()
}

该视图如下所示: 集合视图

The problem is that during the reordering, the cells maintain the dimensions of the original cell at a indexPath, so during the the reordering, the view looks like this: 重新排序 At the moment I've fixed the problem reloading data at the end of the reordering, to recalculate the right dimensions. How can I mantain the right dimensions for the cells also during the interactive movement and reorder custom size cells?

Marcus

This has been bugging me all week so I sat down this evening to try and find a solution. I think what you need is a custom layout manager for your collection view, which can dynamically adjust the layout for each cell as the order is changed.

The following code obviously produces something a lot cruder than your layout above, but fundamentally achieves the behaviour you want: crucially moving to the new layout when the cells are reordered occurs "instantaneously" without any interim adjustments required.

The key to it all is the didSet function in the sourceData variable of the view controller. When this array's value is changed (via pressing the sort button - my crude approximation to your gesture recogniser), this automatically triggers a recalculation of the required cell dimensions which then also triggers the layout to clear itself down and recalculate and the collection view to reload the data.

If you have any questions on any of this, let me know. Hope it helps!

UPDATE: OK, I understand what you are trying to do now, and I think the attached updated code gets you there. Instead of using the in-built interaction methods, I think it is easier given the way I have implemented a custom layout manager to use delegation: when the pan gesture recognizer selects a cell, we create a subview based on that word which moves with the gesture. At the same time in the background we remove the word from the data source and refresh the layout. When the user selects a location to place the word, we reverse that process, telling the delegate to insert a word into the data source and refresh the layout. If the user drags the word outside the collection view or to a non-valid location, the word is simply put back where it began (use the cunning technique of storing the original index as the label's tag).

希望对您有帮助!

[文字由维基百科提供]

import UIKit

class ViewController: UIViewController, bespokeCollectionViewControllerDelegate {

     let sourceText : String = "So Midas, king of Lydia, swelled at first with pride when he found he could transform everything he touched to gold; but when he beheld his food grow rigid and his drink harden into golden ice then he understood that this gift was a bane and in his loathing for gold, cursed his prayer"

    var sourceData : [String]! {
        didSet {
            refresh()
        }
    }
    var sortedCVController : UICollectionViewController!
    var sortedLayout : bespokeCollectionViewLayout!
    var sortButton : UIButton!
    var sortDirection : Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        sortedLayout = bespokeCollectionViewLayout(contentWidth: view.frame.width - 200)
        sourceData = {
            let components = sourceText.components(separatedBy: " ")
            return components
        }()

        sortedCVController = bespokeCollectionViewController(sourceData: sourceData, collectionViewLayout: sortedLayout, frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: view.frame.width - 200, height: view.frame.height - 200)))
        (sortedCVController as! bespokeCollectionViewController).delegate = self
        sortedCVController.collectionView!.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: view.frame.width - 200, height: view.frame.height - 200))

        sortButton = {
            let sB : UIButton = UIButton(frame: CGRect(origin: CGPoint(x: 25, y: 100), size: CGSize(width: 50, height: 50)))
            sB.setTitle("Sort", for: .normal)
            sB.setTitleColor(UIColor.black, for: .normal)
            sB.addTarget(self, action: #selector(sort), for: .touchUpInside)
            sB.layer.borderColor = UIColor.black.cgColor
            sB.layer.borderWidth = 1.0
            return sB
        }()

        view.addSubview(sortedCVController.collectionView!)
        view.addSubview(sortButton)
    }

    func refresh() -> Void {
        let dimensions : [CGSize] = {
            var d : [CGSize] = [CGSize]()
            let font = UIFont.systemFont(ofSize: 17)
            let fontAttributes = [NSFontAttributeName : font]
            for item in sourceData {
                let stringSize = ((item + " ") as NSString).size(attributes: fontAttributes)
                d.append(CGSize(width: stringSize.width, height: stringSize.height))
            }
            return d
        }()

        if self.sortedLayout != nil {
            sortedLayout.dimensions = dimensions
            if let _ = sortedCVController {
                (sortedCVController as! bespokeCollectionViewController).sourceData = sourceData
            }
            self.sortedLayout.cache.removeAll()
            self.sortedLayout.prepare()
            if let _ = self.sortedCVController {

                self.sortedCVController.collectionView?.reloadData()
            }
        }
    }


    func sort() -> Void {
        sourceData = sortDirection > 0 ? sourceData.sorted(by: { $0 > $1 }) : sourceData.sorted(by: { $0 < $1 })
        sortDirection = sortDirection + 1 > 1 ? 0 : 1
    }

    func didMoveWord(atIndex: Int) {
        sourceData.remove(at: atIndex)
    }

    func didPlaceWord(word: String, atIndex: Int) {
        print(atIndex)
        if atIndex >= sourceData.count {
            sourceData.append(word)
        }
        else
        {
            sourceData.insert(word, at: atIndex)
        }

    }

    func pleaseRefresh() {
        refresh()
    }

}

protocol bespokeCollectionViewControllerDelegate {
    func didMoveWord(atIndex: Int) -> Void
    func didPlaceWord(word: String, atIndex: Int) -> Void
    func pleaseRefresh() -> Void
}

class bespokeCollectionViewController : UICollectionViewController {

    var sourceData : [String]
    var movingLabel : UILabel!
    var initialOffset : CGPoint!
    var delegate : bespokeCollectionViewControllerDelegate!

    init(sourceData: [String], collectionViewLayout: bespokeCollectionViewLayout, frame: CGRect) {
        self.sourceData = sourceData
        super.init(collectionViewLayout: collectionViewLayout)

        self.collectionView = UICollectionView(frame: frame, collectionViewLayout: collectionViewLayout)
        self.collectionView?.backgroundColor = UIColor.white
        self.collectionView?.layer.borderColor = UIColor.black.cgColor
        self.collectionView?.layer.borderWidth = 1.0

        self.installsStandardGestureForInteractiveMovement = false

        let pangesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(gesture:)))
        self.collectionView?.addGestureRecognizer(pangesture)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func handlePanGesture(gesture: UIPanGestureRecognizer) {
        guard let _ = delegate else { return }

        switch gesture.state {
        case UIGestureRecognizerState.began:
            guard let selectedIndexPath = self.collectionView?.indexPathForItem(at: gesture.location(in: self.collectionView)) else { break }
            guard let selectedCell : UICollectionViewCell = self.collectionView?.cellForItem(at: selectedIndexPath) else { break }
            initialOffset = gesture.location(in: selectedCell)

            let index : Int = {
                var i : Int = 0
                for sectionCount in 0..<selectedIndexPath.section {
                    i += (self.collectionView?.numberOfItems(inSection: sectionCount))!
                }
                i += selectedIndexPath.row
                return i
            }()


            movingLabel = {
                let mL : UILabel = UILabel()
                mL.font = UIFont.systemFont(ofSize: 17)
                mL.frame = selectedCell.frame
                mL.textColor = UIColor.black
                mL.text = sourceData[index]
                mL.layer.borderColor = UIColor.black.cgColor
                mL.layer.borderWidth = 1.0
                mL.backgroundColor = UIColor.white
                mL.tag = index
                return mL
            }()

            self.collectionView?.addSubview(movingLabel)

            delegate.didMoveWord(atIndex: index)
        case UIGestureRecognizerState.changed:
            if let _ = movingLabel {
                movingLabel.frame.origin = CGPoint(x: gesture.location(in: self.collectionView).x - initialOffset.x, y: gesture.location(in: self.collectionView).y - initialOffset.y)
            }

        case UIGestureRecognizerState.ended:
            print("Interactive movement ended")
            if let selectedIndexPath = self.collectionView?.indexPathForItem(at: gesture.location(in: self.collectionView)) {
                 guard let _ = movingLabel else { return }

                let index : Int = {
                    var i : Int = 0
                    for sectionCount in 0..<selectedIndexPath.section {
                        i += (self.collectionView?.numberOfItems(inSection: sectionCount))!
                    }
                    i += selectedIndexPath.row
                    return i
                }()

                delegate.didPlaceWord(word: movingLabel.text!, atIndex: index)
                UIView.animate(withDuration: 0.25, animations: {
                    self.movingLabel.alpha = 0
                    self.movingLabel.removeFromSuperview()
                    }, completion: { _ in
                        self.movingLabel = nil })
            }
            else
            {
                if let _ = movingLabel {
                    delegate.didPlaceWord(word: movingLabel.text!, atIndex: movingLabel.tag)
                    UIView.animate(withDuration: 0.25, animations: {
                        self.movingLabel.alpha = 0
                        self.movingLabel.removeFromSuperview()
                    }, completion: { _ in
                        self.movingLabel = nil })
                }
            }

        default:
            collectionView?.cancelInteractiveMovement()
            print("Interactive movement canceled")
        }
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        guard !(self.collectionViewLayout as! bespokeCollectionViewLayout).cache.isEmpty else { return 0 }

        return (self.collectionViewLayout as! bespokeCollectionViewLayout).cache.last!.indexPath.section + 1
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        guard !(self.collectionViewLayout as! bespokeCollectionViewLayout).cache.isEmpty else { return 0 }

        var n : Int = 0
        for element in (self.collectionViewLayout as! bespokeCollectionViewLayout).cache {
            if element.indexPath.section == section {
                if element.indexPath.row > n {
                    n = element.indexPath.row
                }
            }
        }
        print("Section \(section) has \(n) elements")
        return n + 1
    }

    override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        let change = sourceData[sourceIndexPath.row]

        sourceData.remove(at: sourceIndexPath.row)
        sourceData.insert(change, at: destinationIndexPath.row)
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)

        // Clean
        for subview in cell.subviews {
            subview.removeFromSuperview()
        }

        let label : UILabel = {
            let l : UILabel = UILabel()
            l.font = UIFont.systemFont(ofSize: 17)
            l.frame = CGRect(origin: CGPoint.zero, size: cell.frame.size)
            l.textColor = UIColor.black

            let index : Int = {
                var i : Int = 0
                for sectionCount in 0..<indexPath.section {
                    i += (self.collectionView?.numberOfItems(inSection: sectionCount))!
                }
                i += indexPath.row
                return i
            }()

            l.text = sourceData[index]
            return l
        }()

        cell.addSubview(label)

        return cell
    }

}


class bespokeCollectionViewLayout : UICollectionViewLayout {

    var cache : [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
    let contentWidth: CGFloat
    var dimensions : [CGSize]!

    init(contentWidth: CGFloat) {
        self.contentWidth = contentWidth

        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepare() -> Void {
        guard self.dimensions != nil else { return }
        if cache.isEmpty {
            var xOffset : CGFloat = 0
            var yOffset : CGFloat = 0

            var rowCount = 0
            var wordCount : Int = 0

            while wordCount < dimensions.count {
                let nextRowCount : Int = {
                    var totalWidth : CGFloat = 0
                    var numberOfWordsInRow : Int = 0

                    while totalWidth < contentWidth && wordCount < dimensions.count {
                        if totalWidth + dimensions[wordCount].width >= contentWidth {
                            break
                        }
                        else
                        {
                            totalWidth += dimensions[wordCount].width
                            wordCount += 1
                            numberOfWordsInRow += 1
                        }

                    }
                    return numberOfWordsInRow
                }()

                var columnCount : Int = 0
                for count in (wordCount - nextRowCount)..<wordCount {
                    let index : IndexPath = IndexPath(row: columnCount, section: rowCount)
                    let newAttribute : UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes(forCellWith: index)
                    let cellFrame : CGRect = CGRect(origin: CGPoint(x: xOffset, y: yOffset), size: dimensions[count])
                    newAttribute.frame = cellFrame
                    cache.append(newAttribute)

                    xOffset += dimensions[count].width
                    columnCount += 1
                }

                xOffset = 0
                yOffset += dimensions[0].height

                rowCount += 1

            }
        }
    }

    override var collectionViewContentSize: CGSize {
        guard !cache.isEmpty else { return CGSize(width: 100, height: 100) }
        return CGSize(width: self.contentWidth, height: cache.last!.frame.maxY)
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var layoutAttributes = [UICollectionViewLayoutAttributes]()
        if cache.isEmpty {
            self.prepare()
        }
        for attributes in cache {
            if attributes.frame.intersects(rect) {
                layoutAttributes.append(attributes)
            }
        }
        return layoutAttributes
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

使用Apache POI Scala编写值后,使用日期打开excel单元时出现问题

使用CsvHelper从CSV文件获取单元格值时出现问题

使用GUI为程序创建ubuntu服务单元文件时出现问题

尝试对使用模拟 Task<TResult> 的方法进行单元测试时出现问题

使用自定义脚本对数组进行升序和降序排序时出现问题

使用自定义字体时出现问题-“无法制作本机字体”

使用模板自定义剑道树视图时出现问题

Phoenix-使用两个自定义主键获取模型时出现问题

导入使用React-Router-Dom的自定义模块组件时出现问题

使用Directshow自定义过滤器时出现问题

使用GLM时出现问题

使用JQuery Toggle时出现问题

使用testcontainer时出现问题

使用viewDidLayoutSubviews时出现问题

使用“清除”命令时出现问题

使用 UISearchResults 过滤时出现问题

使用ejs语法时出现问题

使用多个列表时出现问题

使用SAP网站时出现问题

尝试使用cProfile时出现问题

用状态重新排序组件时出现问题

使用AngularJS自定义排序功能对电子邮件值进行排序时出现问题

Jtable,使用自定义渲染器和系统L&F显示排序图标TableHeader时出现问题

火灾本地通知时显示自定义视图时出现问题

使用URLClassLoader重新加载jar时出现问题

在Visual Studio 2019 C ++中使用CPPUnitTest编写“ DivideByZero”单元测试用例时出现问题

安卓。在多个视图上应用自定义动画时出现问题

将TableColumn对象连接到自定义类时出现问题

尝试为STM32构建自定义MicroPython固件时出现问题