2016-09-25 29 views
9

我想按每個單元格的自定義大小對集合視圖中的單元格重新排序。
在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() 
} 

的看法是這樣的: collection view

問題在重新排序期間,單元格將原始單元格的維度保留在indexPath處,因此在重新排序期間,視圖如下所示: reordering 目前我已經解決了在重新排序結束時重新加載數據的問題,以重新計算正確的維度。 如何在交互式移動和重新訂購自定義尺寸單元格的過程中爲單元維護正確的尺寸?

+0

很抱歉,如果我做的語言森的失誤,我是意大利 – ale00

回答

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

謝謝你的工作,我會試着去了解和實現這一點。我會讓你更新 – ale00

+0

@ ale00沒問題,我很喜歡它的工作。我感謝它相對複雜,但如果你運行它並通過代碼工作,它應該是有道理的。我發現一個定製的集合視圖佈局在這種情況下提供了更多的控制。 – Sparky

+0

我已經運行了代碼,它運行良好,然後我試圖用手勢識別器實現重新排序,但是如果我將實際行中的單元格移動,單元格消失,並且如果我將它移到應用程序崩潰之外。這是我現在的代碼:https://gist.github.com/ale00/a34c575e1d4f6f16774c68a921759d3b,變化從第88行開始。也許我做錯了。 (對不起英文錯誤) – ale00

相關問題