[英]UICollectionView reloadData not functioning properly in iOS 7
我一直在更新我的应用程序以在 iOS 7 上运行,这在大多数情况下进展顺利。 我在不止一个应用程序中注意到UICollectionViewController
的reloadData
方法UICollectionViewController
那样起作用。
我将加载UICollectionViewController
,像UICollectionView
用一些数据填充UICollectionView
。 这在第一次很有效。 但是,如果我请求新数据(填充UICollectionViewDataSource
),然后调用reloadData
,它将查询numberOfItemsInSection
和numberOfSectionsInCollectionView
的数据源,但它似乎没有调用cellForItemAtIndexPath
正确的次数。
如果我将代码更改为仅重新加载一个部分,那么它将正常运行。 改变这些对我来说没有问题,但我认为我不应该这样做。 reloadData
应根据文档重新加载所有可见单元格。
有没有其他人看到这个?
在主线程上强制执行此操作:
dispatch_async(dispatch_get_main_queue(), ^ {
[self.collectionView reloadData];
});
就我而言,数据源中单元格/部分的数量从未改变,我只想重新加载屏幕上的可见内容..
我设法通过调用来解决这个问题:
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
然后:
[self.collectionView reloadData];
我遇到了完全相同的问题,但是我设法找到了问题所在。 在我的情况下,我从collectionView:cellForItemAtIndexPath:调用reloadData :这看起来不正确。
将reloadData 的调用分派到主队列一劳永逸地解决了这个问题。
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
重新加载一些项目对我不起作用。 就我而言,仅因为我使用的 collectionView 只有一个部分,我只需重新加载该特定部分。 这次内容正确地重新加载。 奇怪的是,这仅发生在 iOS 7 (7.0.3) 上
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
我在 iOS 7 上的 reloadData 遇到了同样的问题。经过长时间的调试会话,我发现了问题。
在 iOS7 上, UICollectionView 上的 reloadData 不会取消尚未完成的先前更新(在 performBatchUpdates: 块中调用的更新)。
解决此错误的最佳解决方案是停止当前正在处理的所有更新并调用 reloadData。 我没有找到取消或停止一块 performBatchUpdates 的方法。 因此,为了解决这个错误,我保存了一个标志,指示当前是否有一个 performBatchUpdates 块正在处理。 如果没有当前处理的更新块,我可以立即调用 reloadData 并且一切都按预期工作。 如果有当前正在处理的更新块,我将在 performBatchUpdates 的完整块上调用 reloadData。
// GCD
DispatchQueue.main.async(execute: collectionView.reloadData)
// Operation
OperationQueue.main.addOperation(collectionView.reloadData)
// Operation
NSOperationQueue.mainQueue().addOperationWithBlock(collectionView.reloadData)
我也有这个问题。 巧合的是,我在 collectionview 顶部添加了一个按钮,以强制重新加载进行测试 - 突然间这些方法开始被调用。
也只是添加一些简单的东西
UIView *aView = [UIView new];
[collectionView addSubView:aView];
会导致方法被调用
我还尝试了帧大小 - 瞧,这些方法被调用了。
iOS7 UICollectionView 有很多错误。
你可以使用这个方法
[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];
您可以通过使用以下方法迭代所有部分和行的循环,将UICollectionView
所有indexPath
对象添加到数组arrayOfAllIndexPaths
中
[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];
我希望你理解,它可以解决你的问题。 如果您需要更多解释,请回复。
Shaunti Fondrisi 给出的解决方案几乎是完美的。 但是这样一段代码或类似将UICollectionView
的reloadData()
的执行排队到NSOperationQueue
的mainQueue
确实将执行时间置于运行循环中下一个事件循环的开始,这可能会使UICollectionView
更新为轻弹。
为了解决这个问题。 我们必须将同一段代码的执行时间放在当前事件循环的末尾,而不是下一个事件循环的开始。 我们可以通过使用CFRunLoopObserver
来实现这一点。
CFRunLoopObserver
观察所有输入源等待活动和运行循环的进入和退出活动。
public struct CFRunLoopActivity : OptionSetType {
public init(rawValue: CFOptionFlags)
public static var Entry: CFRunLoopActivity { get }
public static var BeforeTimers: CFRunLoopActivity { get }
public static var BeforeSources: CFRunLoopActivity { get }
public static var BeforeWaiting: CFRunLoopActivity { get }
public static var AfterWaiting: CFRunLoopActivity { get }
public static var Exit: CFRunLoopActivity { get }
public static var AllActivities: CFRunLoopActivity { get }
}
在这些活动中, .AfterWaiting
可以在当前事件循环即将结束时观察到, .BeforeWaiting
可以在下一个事件循环刚刚开始时观察到。
由于每个NSThread
只有一个NSRunLoop
实例并且NSRunLoop
正好驱动NSThread
,我们可以认为来自同一个NSRunLoop
实例的访问永远不会跨线程。
基于之前提到的几点,我们现在可以编写代码:一个基于 NSRunLoop 的任务调度器:
import Foundation
import ObjectiveC
public struct Weak<T: AnyObject>: Hashable {
private weak var _value: T?
public weak var value: T? { return _value }
public init(_ aValue: T) { _value = aValue }
public var hashValue: Int {
guard let value = self.value else { return 0 }
return ObjectIdentifier(value).hashValue
}
}
public func ==<T: AnyObject where T: Equatable>(lhs: Weak<T>, rhs: Weak<T>)
-> Bool
{
return lhs.value == rhs.value
}
public func ==<T: AnyObject>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
return lhs.value === rhs.value
}
public func ===<T: AnyObject>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
return lhs.value === rhs.value
}
private var dispatchObserverKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.DispatchObserver"
private var taskQueueKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskQueue"
private var taskAmendQueueKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskAmendQueue"
private typealias DeallocFunctionPointer =
@convention(c) (Unmanaged<NSRunLoop>, Selector) -> Void
private var original_dealloc_imp: IMP?
private let swizzled_dealloc_imp: DeallocFunctionPointer = {
(aSelf: Unmanaged<NSRunLoop>,
aSelector: Selector)
-> Void in
let unretainedSelf = aSelf.takeUnretainedValue()
if unretainedSelf.isDispatchObserverLoaded {
let observer = unretainedSelf.dispatchObserver
CFRunLoopObserverInvalidate(observer)
}
if let original_dealloc_imp = original_dealloc_imp {
let originalDealloc = unsafeBitCast(original_dealloc_imp,
DeallocFunctionPointer.self)
originalDealloc(aSelf, aSelector)
} else {
fatalError("The original implementation of dealloc for NSRunLoop cannot be found!")
}
}
public enum NSRunLoopTaskInvokeTiming: Int {
case NextLoopBegan
case CurrentLoopEnded
case Idle
}
extension NSRunLoop {
public func perform(closure: ()->Void) -> Task {
objc_sync_enter(self)
loadDispatchObserverIfNeeded()
let task = Task(self, closure)
taskQueue.append(task)
objc_sync_exit(self)
return task
}
public override class func initialize() {
super.initialize()
struct Static {
static var token: dispatch_once_t = 0
}
// make sure this isn't a subclass
if self !== NSRunLoop.self {
return
}
dispatch_once(&Static.token) {
let selectorDealloc: Selector = "dealloc"
original_dealloc_imp =
class_getMethodImplementation(self, selectorDealloc)
let swizzled_dealloc = unsafeBitCast(swizzled_dealloc_imp, IMP.self)
class_replaceMethod(self, selectorDealloc, swizzled_dealloc, "@:")
}
}
public final class Task {
private let weakRunLoop: Weak<NSRunLoop>
private var _invokeTiming: NSRunLoopTaskInvokeTiming
private var invokeTiming: NSRunLoopTaskInvokeTiming {
var theInvokeTiming: NSRunLoopTaskInvokeTiming = .NextLoopBegan
guard let amendQueue = weakRunLoop.value?.taskAmendQueue else {
fatalError("Accessing a dealloced run loop")
}
dispatch_sync(amendQueue) { () -> Void in
theInvokeTiming = self._invokeTiming
}
return theInvokeTiming
}
private var _modes: NSRunLoopMode
private var modes: NSRunLoopMode {
var theModes: NSRunLoopMode = []
guard let amendQueue = weakRunLoop.value?.taskAmendQueue else {
fatalError("Accessing a dealloced run loop")
}
dispatch_sync(amendQueue) { () -> Void in
theModes = self._modes
}
return theModes
}
private let closure: () -> Void
private init(_ runLoop: NSRunLoop, _ aClosure: () -> Void) {
weakRunLoop = Weak<NSRunLoop>(runLoop)
_invokeTiming = .NextLoopBegan
_modes = .defaultMode
closure = aClosure
}
public func forModes(modes: NSRunLoopMode) -> Task {
if let amendQueue = weakRunLoop.value?.taskAmendQueue {
dispatch_async(amendQueue) { [weak self] () -> Void in
self?._modes = modes
}
}
return self
}
public func when(invokeTiming: NSRunLoopTaskInvokeTiming) -> Task {
if let amendQueue = weakRunLoop.value?.taskAmendQueue {
dispatch_async(amendQueue) { [weak self] () -> Void in
self?._invokeTiming = invokeTiming
}
}
return self
}
}
private var isDispatchObserverLoaded: Bool {
return objc_getAssociatedObject(self, &dispatchObserverKey) !== nil
}
private func loadDispatchObserverIfNeeded() {
if !isDispatchObserverLoaded {
let invokeTimings: [NSRunLoopTaskInvokeTiming] =
[.CurrentLoopEnded, .NextLoopBegan, .Idle]
let activities =
CFRunLoopActivity(invokeTimings.map{ CFRunLoopActivity($0) })
let observer = CFRunLoopObserverCreateWithHandler(
kCFAllocatorDefault,
activities.rawValue,
true, 0,
handleRunLoopActivityWithObserver)
CFRunLoopAddObserver(getCFRunLoop(),
observer,
kCFRunLoopCommonModes)
let wrappedObserver = NSAssociated<CFRunLoopObserver>(observer)
objc_setAssociatedObject(self,
&dispatchObserverKey,
wrappedObserver,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var dispatchObserver: CFRunLoopObserver {
loadDispatchObserverIfNeeded()
return (objc_getAssociatedObject(self, &dispatchObserverKey)
as! NSAssociated<CFRunLoopObserver>)
.value
}
private var taskQueue: [Task] {
get {
if let taskQueue = objc_getAssociatedObject(self,
&taskQueueKey)
as? [Task]
{
return taskQueue
} else {
let initialValue = [Task]()
objc_setAssociatedObject(self,
&taskQueueKey,
initialValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return initialValue
}
}
set {
objc_setAssociatedObject(self,
&taskQueueKey,
newValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var taskAmendQueue: dispatch_queue_t {
if let taskQueue = objc_getAssociatedObject(self,
&taskAmendQueueKey)
as? dispatch_queue_t
{
return taskQueue
} else {
let initialValue =
dispatch_queue_create(
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskAmendQueue",
DISPATCH_QUEUE_SERIAL)
objc_setAssociatedObject(self,
&taskAmendQueueKey,
initialValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return initialValue
}
}
private func handleRunLoopActivityWithObserver(observer: CFRunLoopObserver!,
activity: CFRunLoopActivity)
-> Void
{
var removedIndices = [Int]()
let runLoopMode: NSRunLoopMode = currentRunLoopMode
for (index, eachTask) in taskQueue.enumerate() {
let expectedRunLoopModes = eachTask.modes
let expectedRunLoopActivitiy =
CFRunLoopActivity(eachTask.invokeTiming)
let runLoopModesMatches = expectedRunLoopModes.contains(runLoopMode)
|| expectedRunLoopModes.contains(.commonModes)
let runLoopActivityMatches =
activity.contains(expectedRunLoopActivitiy)
if runLoopModesMatches && runLoopActivityMatches {
eachTask.closure()
removedIndices.append(index)
}
}
taskQueue.removeIndicesInPlace(removedIndices)
}
}
extension CFRunLoopActivity {
private init(_ invokeTiming: NSRunLoopTaskInvokeTiming) {
switch invokeTiming {
case .NextLoopBegan: self = .AfterWaiting
case .CurrentLoopEnded: self = .BeforeWaiting
case .Idle: self = .Exit
}
}
}
使用之前的代码,我们现在可以通过这样一段代码将UICollectionView
的reloadData()
的执行分派到当前事件循环的末尾:
NSRunLoop.currentRunLoop().perform({ () -> Void in
collectionView.reloadData()
}).when(.CurrentLoopEnded)
事实上,这样一个基于 NSRunLoop 的任务调度器已经出现在我个人使用的框架之一:Nest 中。 这是它在 GitHub 上的存储库: https : //github.com/WeZZard/Nest
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView reloadData];
[collectionView layoutIfNeeded];
[collectionView reloadData];
});
它对我有用。
首先感谢这个线程,非常有帮助。 我在重新加载数据时遇到了类似的问题,但症状是无法再以永久方式选择特定单元格,而其他单元格可以。 不调用 indexPathsForSelectedItems 方法或等效方法。 调试指出重新加载数据。 我尝试了上面的两个选项; 并最终采用了 ReloadItemsAtIndexPaths 选项,因为其他选项在我的情况下不起作用,或者使集合视图闪烁一毫秒左右。 下面的代码效果很好:
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
NSIndexPath *indexPath;
for (int i = 0; i < [self.assets count]; i++) {
indexPath = [NSIndexPath indexPathForItem:i inSection:0];
[indexPaths addObject:indexPath];
}
[collectionView reloadItemsAtIndexPaths:indexPaths];`
对我来说,使用所有可见项目调用reloadItems(at:)
,而不是reloadData
。
collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems)
(liamnichols 的答案的 Swift 版本)
我在 iOS 8.1 sdk 中也发生了这种情况,但是当我注意到即使在更新datasource
,方法numberOfItemsInSection:
也没有返回新的项目数时,我得到了正确的numberOfItemsInSection:
。 我更新了计数并让它工作。
你设置 UICollectionView.contentInset 了吗? 去掉左右edgeInset,去掉后一切正常,但iOS8.3依然存在bug。
检查每个 UICollectionView Delegate 方法是否按照您的预期执行。 例如,如果
collectionView:layout:sizeForItemAtIndexPath:
不返回有效大小,重新加载将不起作用...
试试这个代码。
NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];
if (visibleIdx.count) {
[self.collectionView reloadItemsAtIndexPaths:visibleIdx];
}
这是它在Swift 4 中对我的作用
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = campaignsCollection.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
cell.updateCell()
// TO UPDATE CELLVIEWS ACCORDINGLY WHEN DATA CHANGES
DispatchQueue.main.async {
self.campaignsCollection.reloadData()
}
return cell
}
inservif (isInsertHead) {
[self insertItemsAtIndexPaths:tmpPoolIndex];
NSArray * visibleIdx = [self indexPathsForVisibleItems];
if (visibleIdx.count) {
[self reloadItemsAtIndexPaths:visibleIdx];
}
}else if (isFirstSyncData) {
[self reloadData];
}else{
[self insertItemsAtIndexPaths:tmpPoolIndex];
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.