UITableView动画在排序时重新排序

时间:2011-02-21 21:17:24

标签: ios uitableview animation

我有一个表,用户可以通过两种不同的方式进行排序。当用户在这两种排序模式之间切换时,我希望重新排序为动画。虽然UITableView的方法提供了插入,删除和重新加载行的动画帮助,但我没有看到任何可以让我为行移动设置动画的内容。

我错过了什么吗?

假设我不是,有没有人有关于如何做到这一点的示例代码?

感谢。

4 个答案:

答案 0 :(得分:34)

我知道这是一个较老的问题,但我遇到了同样的问题并且认为我找到了一个解决方案,所以我想我会分享以防其他人在这里遇到同样的问题。我也不确定它的解决方案有多棒,但它似乎对我有用。

我的TableView从名为_objects的数组中获取数据。在我的排序方法中,我首先创建另一个数组,它是数据数组的精确副本:

    NSArray *objectsBeforeSort = [NSArray arrayWithArray:_objects];

然后我使用我已经创建的排序描述符对数据数组_objects进行排序。在我的数组上调用适当的排序方法后,我使用它来为表更新设置动画:

[self.tableView beginUpdates];

for (int i = 0; i < _objects.count; i++)
{
    // newRow will get the new row of an object.  i is the old row.
    int newRow = [_objects indexOfObject:objectsBeforeSort[i]];
    [self.tableView moveRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0] toIndexPath:[NSIndexPath indexPathForRow:newRow inSection:0]];
}


[self.tableView endUpdates];

我还没有广泛测试过这段代码,但它似乎给出了理想的结果。

答案 1 :(得分:1)

您是否尝试过使用moveRowAtIndexPath:toIndexPath:在beginUpdates-endUpdates块中?它似乎是开发人员文档目前推荐的内容。如果它本身不起作用,你也可以尝试将它包装在[UIView animateWithDuration:animations:]块中。

链接到UITableView的ADC文档: https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html#//apple_ref/doc/c_ref/UITableView

答案 2 :(得分:0)

提出的解决方案有一些缺点,这使我怀疑是否需要动画排序。

  • 假定数组中的所有值都是唯一的。一个数字列表,其中相同的数字出现多次,它将为此数字的每个实例返回相同的索引。

  • 排序将完成一个新的数组。内存需求增加了一倍。

  • 该算法效率低下。对于排序数组中的每个值,都将扫描原始数组以获取值的来源。

  • 即使使用高效的算法,谁也希望看到10000行动画?实例化10000个视图,甚至500个视图,都是不可取的。

话虽这么说,如果必须这样做,一个更好的解决方案是创建自己的合并排序函数,将其排序就位,或者从Swift存储库中获取排序实现。 创建一个单独的类,以跟踪数组中每个索引的移动。最后,仅对可见的行加超范围进行动画处理。表格视图包含此信息。

此解决方案效率更高,但是在快速排序算法如此高效的情况下,跟踪索引移动的开销却不小。在阵列中预先分配所需的空间将在某种程度上减轻它。另外,它仅在后备存储是数组时才起作用。如果您对核心数据进行排序,则无法使用。如果表的值小于1000,我可能只会做动画。

答案 3 :(得分:0)

我很清楚这是一个比较老的问题,但是似乎有一些基于某些评论日期的内容。我将根据该线程中的评论发布一个解决方案。我很清楚这可能无法很好地缩放到具有很多行(无论在这里是什么意思)的表,但是对于相对较短的列表(在我的示例中最多为26行),它工作得很好并且提供了合理的动画效果。

不知道这是否对任何人都有帮助,但希望有帮助。

import UIKit

class Record : Comparable
{
  static func < (lhs: Record, rhs: Record) -> Bool { return lhs.score > rhs.score }
  static func == (lhs: Record, rhs: Record) -> Bool { return lhs.score == rhs.score }
  
  let name : String
  var score : Int
  
  init(_ name:String, _ score:Int=10) {
    self.name = name
    self.score = score
  }
  
  var scoreString : String { return "\(score)" }
}


class Move
{
  var from: Int? = nil
  var to: Int? = nil
  init(from:Int) { self.from = from }
  init(to:Int) { self.to = to }
}


class ViewController: UIViewController
{
  @IBOutlet weak var tableView: UITableView!
  
  var records = Array<Record>()
  var pool    = Array<Record>()
  
  override func viewDidLoad()
  {
    super.viewDidLoad()

    for name in ["Adam", "Burt", "Chuck", "Dave", "Ernie", "Frank", "Gem", "Hank"]
    {
      records.append( Record(name) )
    }

    for name in [ "Ivan", "Jake", "Kyle", "Lenny", "Mike", "Ned", "Oscar", "Pete", "Quay", "Rob", "Steve", "Tom", "Uke", "Vlad", "Wyatt", "Xavier", "Yoda", "Zack" ]
    {
      pool.append( Record(name) )
    }
  }
  
  
  @IBAction func handleRand(_ sender:UIButton)
  {
    var moves = Dictionary<String,Move>()
    
    // Note everyone's current position in the ranking
    for i in 0..<records.count {
      moves[ records[i].name ] = Move(from:i)
    }
    
    // randomly move some players to the sidelines
    for _ in (0..<Int.random(in: 2...4)) {
      if records.count > 0 {
        let ri = Int.random(in: 0..<records.count)
        let r = records.remove(at: ri)
        pool.append(r)
      }
    }
    // randomly pull some people from the sidelines into the game
    for _ in (0..<Int.random(in: 2...4)) {
      if pool.count > 0 {
        let ri = Int.random(in: 0..<pool.count)
        let r = pool.remove(at: ri)
        records.append(r)
      }
    }
    
    // randomly update everyones' scores
    for r in records { r.score = (0..<100).randomElement()! }
    
    // sort the records from high to low score
    records.sort()

    // update our list of everyone's position
    //   note that some may be leaving/entering active play
    for i in 0..<records.count {
      if let m = moves[ records[i].name ] { m.to = i }
      else { moves[ records[i].name ] = Move(to:i) }
    }
    
    // figure out how each of the table rows will need to be updated, move, delete, or insert
    var remove = Array<IndexPath>()
    var insert = Array<IndexPath>()
    var move  = Array<(IndexPath,IndexPath)>()
    
    for (_,m) in moves
    {
      if let from = m.from, let to = m.to
      {
        move.append( (IndexPath(row:from,section:0), IndexPath(row:to,section:0)) )
      }
      else if let from = m.from
      {
        remove.append( IndexPath(row:from, section:0) )
      }
      else if let to = m.to
      {
        insert.append( IndexPath(row:to, section:0) )
      }
    }
    
    // PERFORM THE MOVES, INSERTS, and DELETES
    tableView.beginUpdates()
    
    tableView.deleteRows(at: remove, with: .fade)
    tableView.insertRows(at: insert, with: .fade)
    for (from,to) in move
    {
      tableView.moveRow(at: from, to: to)
    }
    
    tableView.endUpdates()
    
    // update the cells to show the new scores
    for i in 0..<records.count
    {
      if let c = tableView.cellForRow(at: IndexPath(row: i, section: 0))
      {
        let r = records[i]
        c.detailTextLabel?.text = r.scoreString
        c.contentView.backgroundColor = ( r.score > 50 ? UIColor.yellow : UIColor.systemTeal )
      }
    }
  }
  
}

extension ViewController : UITableViewDelegate, UITableViewDataSource
{
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
  {
    records.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
  {
    let cell = tableView.dequeueReusableCell(withIdentifier: "recordCell", for: indexPath)
    
    let r = records[indexPath.row]
    
    cell.textLabel?.text = r.name
    cell.detailTextLabel?.text = r.scoreString
    
    let cv = cell.contentView
    let l = cv.layer
    l.cornerRadius = 8.0
    l.borderColor = UIColor.black.cgColor
    l.borderWidth = 1.0
    l.masksToBounds = true
    
    cv.backgroundColor = ( r.score > 50 ? UIColor.yellow : UIColor.systemTeal )
    
    return cell
  }
  
}