我有一个表,用户可以通过两种不同的方式进行排序。当用户在这两种排序模式之间切换时,我希望重新排序为动画。虽然UITableView的方法提供了插入,删除和重新加载行的动画帮助,但我没有看到任何可以让我为行移动设置动画的内容。
我错过了什么吗?
假设我不是,有没有人有关于如何做到这一点的示例代码?
感谢。
答案 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
}
}