我想为每个单元格的自定义大小重新排序集合视图中的单元格 在Collection View的每个单元格中都有一个带有单词的标签 我用这段代码设置了每个单元格的维度:
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()
}
问题是在重新排序期间,单元格在indexPath处维护原始单元格的尺寸,因此在重新排序期间,视图如下所示: 目前我已经修复了在重新排序结束时重新加载数据的问题,以重新计算正确的维度。 如何在交互式移动过程中对单元格保持正确的尺寸并重新排序自定义尺寸单元格?
答案 0 :(得分:3)
这一直困扰着我,所以我今天晚上坐下来试图寻找解决方案。我认为您需要的是集合视图的自定义布局管理器,它可以在订单更改时动态调整每个单元格的布局。
下面的代码显然会产生比上面的布局更多的东西,但从根本上实现了你想要的行为:当重新排序单元时,关键地转移到新的布局“瞬间”发生,而不需要任何临时调整。
这一切的关键是视图控制器的sourceData变量中的didSet函数。当这个数组的值发生变化时(通过按下排序按钮 - 粗略逼近你的手势识别器),这会自动触发重新计算所需的单元格尺寸,然后触发布局自行清除并重新计算,并重新加载集合视图数据。
如果您对此有任何疑问,请与我们联系。希望能帮助到你!
更新:好的,我明白你现在要做什么,我认为附加的更新代码可以帮到你。我没有使用内置的交互方法,我认为使用自定义布局管理器来使用委托的方式更容易:当平移手势识别器选择一个单元格时,我们创建一个基于该单词移动的子视图。手势。同时在后台我们从数据源中删除该单词并刷新布局。当用户选择放置单词的位置时,我们反转该过程,告诉委托将单词插入数据源并刷新布局。如果用户将单词拖到集合视图之外或者拖到无效位置,则只需将单词放回到开始的位置(使用将原始索引存储为标签标签的狡猾技术)。
希望能帮到你!
[文字由维基百科提供]
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
}
}