Ios UITableView在排序时设置行重新排序动画

Ios UITableView在排序时设置行重新排序动画,ios,uitableview,animation,Ios,Uitableview,Animation,我有一个表,用户可以用两种不同的方式进行排序。当用户在这两种排序模式之间切换时,我希望重新排序设置动画。虽然UITableView的方法提供了插入、删除和重新加载行的动画帮助,但我看不到任何可以设置行移动动画的方法 我错过什么了吗 假设我不是,有没有人有关于如何做到这一点的示例代码 谢谢。您是否尝试过在BeginUpdate endUpdates块中使用MoveRowatineXpath:toIndexPath:?这似乎是开发者文档目前推荐的。如果它本身不起作用,您也可以尝试将其包装在[UIVi

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

我错过什么了吗

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


谢谢。

您是否尝试过在BeginUpdate endUpdates块中使用MoveRowatineXpath:toIndexPath:?这似乎是开发者文档目前推荐的。如果它本身不起作用,您也可以尝试将其包装在[UIView animateWithDuration:animations:]块中

链接到UITableView的ADC文档:

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

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

    NSArray *objectsBeforeSort = [NSArray arrayWithArray:_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];
[self.tableView开始更新];
对于(int i=0;i<\u objects.count;i++)
{
//newRow将获取对象的新行。i是旧行。
int newRow=[_objectsindexofobject:objectsBeforResort[i]];
[self.tableView moverowatinexpath:[nsindepath-indexPathForRow:i分区:0]到indepath:[nsindepath-indexPathForRow:newRow分区:0];
}
[self.tableView endUpdates];

我还没有对这段代码进行过广泛的测试,但它似乎给出了期望的结果。

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

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

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

  • 算法效率很低。对于排序数组中的每个值,将扫描原始数组以查找值的原点

  • 即使有一个高效的算法,谁想看到10000行动画?实例化10000个视图,甚至500个视图,是不可取的

也就是说,如果必须这样做,一个更好的解决方案是创建自己的合并排序函数,在适当的位置进行排序,或者从Swift存储库获取排序实现。 创建一个单独的类来跟踪数组中每个索引的移动。最后,仅为可见行加上超范围设置动画。表视图包含此信息


这种解决方案效率更高,但当swift排序算法如此高效时,跟踪索引移动的开销非常大。在阵列中预先分配所需的空间将在一定程度上缓解这一问题。此外,它仅在备份存储为阵列时工作。如果您对核心数据进行排序,则它不起作用。如果表中的值小于1000,我可能只会制作动画。

我很清楚这是一个较老的问题,但根据一些注释的日期,它似乎有一些分支。我将发布一个我根据这个帖子中的评论提出的解决方案。我很清楚,这可能无法很好地扩展到有很多行的表(不管这意味着什么),但是对于相对较短的列表(在我的示例中最多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
  }
  
}
导入UIKit
班级成绩:可比
{
静态函数<(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
变量分数:Int
init(u名称:String,uu分数:Int=10){
self.name=名称
self.score=分数
}
var scoreString:String{return“\(score)”}
}
阶级运动
{
变量自:Int?=nil
变量到:Int?=nil
init(from:Int){self.from=from}
init(to:Int){self.to=to}
}
类ViewController:UIViewController
{
@IBVAR表格视图:UITableView!
var记录=数组()
变量池=数组()
重写func viewDidLoad()
{
super.viewDidLoad()
在[“亚当”、“伯特”、“查克”、“戴夫”、“厄尼”、“弗兰克”、“宝石”、“汉克”中的名字]
{
记录。追加(记录(名称))
}
在[“Ivan”、“Jake”、“Kyle”、“Lenny”、“Mike”、“Ned”、“Oscar”、“Pete”、“Quay”、“Rob”、“Steve”、“Tom”、“Uke”、“Vlad”、“Wyatt”、“Xavier”、“Yoda”、“Zack”]中的名字]
{
pool.append(记录(名称))
}
}
@iAction func handleRand(\发送方:UIButton)
{
var moves=Dictionary()
//注意每个人在排名中的当前位置

对于0中的i..tjf有一个很好的答案。为什么不给他一些“接受答案”的爱呢?这种方法效率相当低。你不仅要经历排序的复杂性,而且还要循环遍历每个对象并调用indexOfObject:在每个对象上也循环到O(n)