UICollectionView reloadData在iOS 7中无法正常工作

我一直在更新我的应用程序,以在大多数情况下顺利运行的iOS 7上运行。 我已经注意到,在多个应用程序中, UICollectionViewControllerreloadData方法的UICollectionViewController不像以前那样。

我将加载UICollectionViewController ,用一些正常的数据填充UICollectionView 。 这在第一次很好。 但是,如果我请求新的数据(填充UICollectionViewDataSource ),然后调用reloadData ,它将查询numberOfItemsInSectionnumberOfSectionsInCollectionView的数据源,但它似乎并没有调用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相同的问题。长时间debugging后,我发现问题所在。

在iOS7上,UICollectionView上的reloadData不会取消以前尚未完成的更新(在performBatchUpdates:block内部调用的更新)。

解决这个错误的最佳解决scheme是停止当前处理的所有更新并调用reloadData。 我没有find取消或停止performBatchUpdates块的方法。 因此,为了解决这个错误,我保存了一个标志,表明是否有一个正在处理的performBatchUpdates块。 如果没有更新块正在处理,我可以立即调用reloadData,一切按预期工作。 如果有一个正在处理的更新块,我将在完整的performBatchUpdates块上调用reloadData。

Swift 2:

 NSOperationQueue.mainQueue().addOperationWithBlock(collectionView.reloadData) 

Swift 3:

 // GCD OperationQueue.main.addOperation(collectionView.reloadData) // Dispatch Queue DispatchQueue.main.async(execute: collectionView.reloadData) 

我也有这个问题。 巧合的是,我在collectionview的顶部添加了一个button,以强制重新加载进行testing – 突然间,方法开始被调用。

也只是添加一些简单的

 UIView *aView = [UIView new]; [collectionView addSubView:aView]; 

会导致方法被调用

我也玩过框架大小 – 瞧,这些方法正在被调用。

iOS7 UICollectionView有很多错误。

你可以使用这个方法

 [collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths]; 

通过使用下面的方法遍历所有部分和行的循环,可以将indexPath所有indexPath对象添加到数组arrayOfAllIndexPaths

 [aray addObject:[NSIndexPath indexPathForItem:j inSection:i]]; 

我希望你能理解,并能解决你的问题。 如果您需要更多的解释,请回复。

Shaunti Fondrisi给出的解决scheme几乎是完美的。 但是,像UICollectionViewreloadData()NSOperationQueuemainQueue这样的一段代码或代码确实把执行时间放在运行循环中的下一个事件循环的开始,这可能会使UICollectionView更新轻弹。

解决这个问题。 我们必须把同一段代码的执行时间放到当前事件循环的结尾,而不是下一个的开始。 我们可以通过使用CFRunLoopObserver来实现这一点。

CFRunLoopObserver观察所有input源等待活动和运行循环的进入和退出活动。

 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 } } 

在这些活动中,当当前事件循环即将结束时,可以观察到.BeforeWaiting ,并且在下一个事件循环刚刚开始时可以观察到.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 } } } 

通过之前的代码,我们现在可以通过这样的代码将UICollectionViewreloadData()的执行调度到当前事件循环的结尾:

 NSRunLoop.currentRunLoop().perform({ () -> Void in collectionView.reloadData() }).when(.CurrentLoopEnded) 

事实上,这样一个基于NSRunLoop的任务调度器已经在我个人使用的框架之一:巢。 这里是它的GitHub仓库: https : //github.com/WeZZard/Nest

首先感谢这个线程,非常有帮助。 我有一个与重新加载数据类似的问题,除了症状是特定的细胞不能再被永久的select,而其他人可以。 没有调用indexPathsForSelectedItems方法或等价物。 debugging指出重新加载数据。 我尝试了上面的两个选项。 并最终采用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];` 

它也发生在我的iOS 8.1 sdk中,但是当我发现即使在更新datasource ,方法numberOfItemsInSection:也没有返回新的项数。 我更新了计数,并使其工作。

你设置UICollectionView.contentInset? 删除左边缘和右边缘,删除它们之后一切正常,错误依然存在于iOS8.3中。

检查每个UICollectionView委托方法是否符合您的期望。 例如,如果

 collectionView:layout:sizeForItemAtIndexPath: 

不会返回有效的大小,重新加载将无法正常工作…

试试这个代码。

  NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems]; if (visibleIdx.count) { [self.collectionView reloadItemsAtIndexPaths:visibleIdx]; }