UICollectionView reloadData在iOS 7中无法正常运行

时间:2013-09-13 23:54:06

标签: ios uicollectionview ios7

我一直在更新我的应用程序以在iOS 7上运行,这在大多数情况下都很顺利。我注意到在多个应用中,reloadData的{​​{1}}方法的行为与以往不同。

我将加载UICollectionViewController,正常填充UICollectionViewController一些数据。这在第一次很好用。但是,如果我请求新数据(填充UICollectionView),然后调用UICollectionViewDataSource,它将查询reloadDatanumberOfItemsInSection的数据来源,但似乎没有拨打numberOfSectionsInCollectionView适当的次数。

如果我将代码更改为仅重新加载一个部分,那么它将正常运行。这对我来说没有问题,但我认为不应该这样做。 cellForItemAtIndexPath应根据文档重新加载所有可见单元格。

有没有人见过这个?

18 个答案:

答案 0 :(得分:71)

在主线程上强制执行此操作:

dispatch_async(dispatch_get_main_queue(), ^ {
    [self.collectionView reloadData];
});

答案 1 :(得分:63)

就我而言,数据源中的单元格/节的数量从未改变过,我只是想重新加载屏幕上的可见内容。

我设法通过调用来解决这个问题:

[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];

然后:

[self.collectionView reloadData];

答案 2 :(得分:26)

我有完全相同的问题,但我设法找到了错误的原因。 在我的情况下,我从 collectionView:cellForItemAtIndexPath:中调用 reloadData ,这看起来不正确。

reloadData 的调用调度到主队列,一次又一次地修复了问题。

  dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView reloadData];
  });

答案 3 :(得分:19)

重新加载一些项目并不适合我。在我的情况下,只是因为我使用的collectionView只有一个部分,我只是重新加载该特定部分。 这次正确地重新加载内容。 奇怪的是,这只发生在iOS 7(7.0.3)

[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];

答案 4 :(得分:12)

斯威夫特4 - 3

// GCD    
DispatchQueue.main.async(execute: collectionView.reloadData)

// Operation
OperationQueue.main.addOperation(collectionView.reloadData)

Swift 2

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

答案 5 :(得分:11)

我在iOS 7上遇到了与reloadData相同的问题。经过长时间的调试,我发现了问题。

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

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

答案 6 :(得分:4)

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

还要添加像

这样简单的东西
UIView *aView = [UIView new];
[collectionView addSubView:aView];

会导致调用方法

我也玩过框架尺寸 - 而且这些方法被调用了。

iOS7 UICollectionView存在很多错误。

答案 7 :(得分:3)

您可以使用此方法

[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];

您可以使用以下方法迭代所有部分和行的循环,将indexPath的所有UICollectionView个对象添加到数组arrayOfAllIndexPaths

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

我希望你明白,它可以解决你的问题。如果您需要更多解释,请回复。

答案 8 :(得分:3)

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

在这些活动中,当前事件循环即将结束时可以观察到NSRunLoop,并且当下一个事件循环刚刚开始时可以观察到NSThread

由于NSRunLoop只有一个NSThread个实例,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个实例永远不要越过线程。

基于前面提到的点,我们现在可以编写代码:基于NSRunLoop的任务调度程序:

reloadData()

使用之前的代码,我们现在可以通过这样的代码将NSRunLoop.currentRunLoop().perform({ () -> Void in collectionView.reloadData() }).when(.CurrentLoopEnded) 的{​​{1}}的执行分派到当前事件循环的末尾:

<TypeProjection ID="TypeProjection.RFCMinor" Accessibility="Public" Type="RFC.Minor.RFCMinor">
      <Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemHasFileAttachment']$" Alias="FileAttachments">
        <Component Path="$Target/Path[Relationship='SupportingItem!System.FileAttachmentAddedByUser']$" Alias="FileAttachmentAddedBy" />
      </Component>
    </TypeProjection>

事实上,这样一个基于NSRunLoop的任务调度程序已经在我个人使用过的框架之一:Nest。这是GitHub上的存储库:https://github.com/WeZZard/Nest

答案 9 :(得分:1)

斯威夫特 5

对我来说,使用所有可见项目调用 reloadItems(at:) 而不是 reloadData

collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems)

(liamnichols 答案的 Swift 版本)

答案 10 :(得分:1)

首先感谢这个帖子,非常有帮助。我有一个与Reload Data类似的问题,除了症状是特定细胞不能再以永久方式选择,而其他细胞则可以。不调用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];`

答案 11 :(得分:0)

检查每个UICollectionView Delegate方法是否符合您的预期。例如,如果

collectionView:layout:sizeForItemAtIndexPath:

没有返回有效尺寸,重新加载无法正常工作......

答案 12 :(得分:0)

您是否设置了UICollectionView.contentInset?删除左右edgeInset,删除后一切正常,iOS8.3中仍然存在bug。

答案 13 :(得分:0)

在iOS 8.1 sdk中也发生过这种情况,但是当我注意到即使在更新datasource后,方法numberOfItemsInSection:没有返回新的项目数,我也弄错了。我更新了计数并使其正常工作。

答案 14 :(得分:0)

试试这段代码。

 NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];

    if (visibleIdx.count) {
        [self.collectionView reloadItemsAtIndexPaths:visibleIdx];
    }

答案 15 :(得分:0)

 dispatch_async(dispatch_get_main_queue(), ^{

            [collectionView reloadData];
            [collectionView layoutIfNeeded];
            [collectionView reloadData];


        });

它对我有用。

答案 16 :(得分:0)

以下是 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
}

答案 17 :(得分:-1)

inservif (isInsertHead) {
   [self insertItemsAtIndexPaths:tmpPoolIndex];
   NSArray * visibleIdx = [self indexPathsForVisibleItems];
   if (visibleIdx.count) {
       [self reloadItemsAtIndexPaths:visibleIdx];
   }
}else if (isFirstSyncData) {
    [self reloadData];
}else{
   [self insertItemsAtIndexPaths:tmpPoolIndex];
}