我有一个应用程序,其中UITableView的数据源每30秒由一个后台线程从远程服务器更新。
如果用户正在滚动tableView或者tableView正在处理reloadTableView:
,则会发生崩溃。崩溃的原因是崩溃时表中的行数与重绘开始时的行数不匹配。
当请求的TableView单元格超出范围时会发生另一次崩溃,因为在调用时间numberofTableViewCells:
和调用时间cellfForRowAtIndexPath
之间,数据模型已更改且单元格不再存在。
tableView的数据从后台线程更新。当数据从服务器加载时,用户仍然可以与tableView进行交互,但现在导致崩溃。
在更新tableViewDataSource时,如何阻止tableView滚动或重新加载? 这种情况的最佳做法是什么?
感谢。
答案 0 :(得分:18)
简短的回答是,您需要缓冲数据模型中的数据,并且仅在未滚动表时使用新数据更新表。这需要在数据模型中完成,因为数据源委托不知道数据模型中的内容或更改的内容。
然而,从UI设计的角度来看,在用户滚动时主动更新表会使用户迷失方向。用户会认为他们位于桌子的顶部/中部/底部,然后突然发现自己位于底部/顶部/中部。用户会认为他们已经查看了表格的一个部分中的所有数据,但实际上该表格将添加他们需要查看的内容。例如,如果表是按字母顺序排列的名称列表,则用户检查所有以“U”开头的名称,看到没有名称,然后检查表中的其他位置。同时,当用户在其他地方寻找时,该表以新名称无形地更新“U”部分。即使用户理解表是动态更新的(大多数都没有),他们也必须不断滚动检查几乎整个表,看看有什么变化。
更好的UI设计是为用户提供更新的能力。在栏中放置“更新”或“新数据可用”按钮,然后将其设置为在新数据到达时显示。单击按钮后,冻结表,更新,然后让用户恢复交互。在视觉上标记添加的行也是一个很好的设计。
这将使用户更容易理解用户界面,同时解决崩溃问题。
如果您不使用Core Data,则为了实现表的实时和不可见更新。在调用– tableView:numberOfRowsInSection
之前,您需要冻结调用– numberOfSectionsInTableView:
或– tableView:cellForRowAtIndexPath:
。
由于首先调用– tableView:numberOfRowsInSection
,我会在那里调用首次更新,然后冻结数据模型。这样,数据模型将返回适当数量的节和行。
我认为你必须将数据模型划分为两个部分,其中一个部分将缓冲输入数据,另一部分将命令数据显示。您的更新方法应将所有已完成的缓冲数据移动到显示数据部分。
此外,您可能需要为用户何时不移动表设置计时器。如果没有主动操作表,则计时器应调用update方法,然后应强制更新。
如果您使用核心数据,则可以使用NSFetchedResultController
,它的委托方法将在数据模型更改时通知您。它应该返回正确更新的正确部分和行信息。以这种方式驱动更新表非常容易。但是,它不能很快克服数据进入模型的问题,以至于模型在方法调用之间发生变化。您仍然需要冻结和/或减慢模型。但是,你不需要计时器。
核心数据是您最好的选择,但即使如此,它也很难实现,因为您正在尝试针对UI的内容做一些事情,因此API不会轻易支持它。
回顾这个答案,我发现我忽略了[UITableView beginUpdates]
,它会在添加或删除行时冻结表的配置。它与[UITableView endUpdates]
配对,以包含用户界面中的更改。
答案 1 :(得分:5)
从后台线程调用GUI对象(如UITableView)上的方法是不安全的。
您需要执行以下操作:
[ tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]