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


93

我一直在更新自己的应用程序,使其可以在iOS 7上正常运行。我已经在多个应用程序中注意到reloadData,a 的方法UICollectionViewController并不像以前那样起作用。

我将正常加载UICollectionViewController,并填充UICollectionView一些数据。这在第一时间效果很好。但是,如果我请求新数据(填充UICollectionViewDataSource),然后调用reloadData,它将为numberOfItemsInSection和查询数据源numberOfSectionsInCollectionView,但似乎调用cellForItemAtIndexPath的次数不正确。

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

其他人看到了吗?


5
同样,它在iOS7GM中也可以正常工作。我注意到reloadData在viewDidAppear之后调用似乎可以解决该问题,其可怕的解决方法并需要修复。我希望有人在这里帮忙。
jasonIM 2013年

1
有同样的问题。以前在iOS6中正常工作的代码。现在即使返回正确数量的单元格也不会调用cellforitematindexpath
Avner Barr

这个问题在7.0之后的版本中已解决吗?
William Jockusch 2014年

我仍然面临与此问题相关的问题。
阿尼尔2014年

即时更改[collectionView setFrame]后出现类似的问题;总是使一个单元出队,而与数据源中的数目无关。在这里尝试了所有其他内容,并且无法解决。
RegularExpression 2014年

Answers:


72

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

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

1
我不确定是否可以解释更多。经过搜索,研究,测试和探测。我觉得这是iOS 7的错误。强制主线程将运行所有与UIKit相关的消息。从另一个视图控制器弹出视图时,我似乎遇到了这个问题。我刷新viewWillAppear上的数据。我可以看到数据和集合视图重新加载调用,但是UI没有更新。强制执行主线程(UI线程),然后魔术般开始工作。这仅仅是在IOS 7
Shaunti Fondrisi

6
没什么意义,因为您不能在主线程之外调用reloadData(您不能在主线程之外更新视图),所以这可能是一种副作用,由于某些竞争条件而导致您想要的东西。
拉斐尔·奥利维拉

7
从主队列中分派主队列只会将执行延迟到下一个运行循环,从而使当前排队的所有内容都有机会先执行。
2015年

2
谢谢!!仍然不明白Joony参数是否正确,因为核心数据请求消耗了时间,并且答复被延迟,或者因为我正在willDisplayCell上重新加载数据。
FidelLópez2015年

1
一直以来都哇,这仍然出现。这确实是竞争条件,或与视图事件生命周期有关。视图“将”出现将已经绘制。很好的洞察力乔尼,谢谢。认为我们可以最终将此项目设置为“已回答”吗?
Shaunti Fondrisi

64

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

我设法通过以下方法解决了这个问题:

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

然后:

[self.collectionView reloadData];

6
该行导致我的应用程序崩溃-“ ***在[[UICollectionView _endItemAnimations],/ SourceCache
UIKit_Sim /

@Lugubrious您可能正在同时执行其他动画..尝试将它们放在一起performBatchUpdates:completion:吗?
liamnichols 2014年

这对我有用,但是我不确定我为什么要这样做。知道是什么问题吗?
乔恩·埃文斯

@JonEvans不幸的是我不知道..我相信这是iOS中的某种错误,不确定是否已在以后的版本中解决,尽管我从那时以来一直未进行测试,并且遇到的项目不是我的问题更长了:)
liamnichols 2014年

1
这个bug简直就是胡说八道!当我重新加载collectionView时,只有当我的收藏夹中有一种特定类型的单元格时,我的所有单元格才会随机消失。我在上面花了两天时间,因为我不知道发生了什么,现在我已经应用了您的解决方案并且它可以工作,但我仍然不明白为什么它现在可以工作。真令人沮丧!无论如何,谢谢您的帮助:D !!
Cyber​​Dandy 2014年

26

我遇到了完全相同的问题,但是我设法找到了问题所在。就我而言,我是从collectionView:cellForItemAtIndexPath:中调用reloadData的,这似乎是不正确的。

reloadData的调用分派到主队列可以永久解决此问题。

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

1
您能否告诉我[self.collectionData.collectionViewLayout invalidateLayout]的这一行是什么?
iOSDeveloper 2014年

这也为我解决了-在我的情况下reloadData,是由变更观察员调用的。
sudo make install

这也适用于collectionView(_:willDisplayCell:forItemAtIndexPath:)
Stefan Arambasich '16

20

重新加载某些项目对我不起作用。就我而言,并且仅因为我使用的collectionView只有一个部分,所以我只是重新加载了该特定部分。这次内容已正确重新加载。奇怪这仅发生在iOS 7(7.0.3)上

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

12

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

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

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


您在performBatchUpdate内部执行所有更新的位置?有进有出?全力以赴?非常有趣的帖子。
VaporwareWolf 2014年

我在NSFetchedResultsController中使用集合视图来显示CoreData中的数据。当NSFetchedResultsController委托通知更改时,我将收集所有更新,并在performBatchUpdates中调用它们。更改NSFetchedResultsController请求谓词后,必须调用reloadData。
user2459624 2014年

这实际上是对这个问题的一个很好的答案。如果运行reloadItems()(动画),然后运行reloadData(),它将跳过单元格。
生物

12

迅捷5 – 4 – 3

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

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

迅捷2

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

4

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

还只是添加一些简单的东西

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

会导致方法被调用

我还尝试了帧大小-瞧,方法被调用了。

iOS7 UICollectionView有很多错误。


我很高兴看到(以证书的方式)其他人也遇到了这个问题。感谢您的解决方法。
VaporwareWolf

3

您可以使用此方法

[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];

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

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

希望您能理解,并且可以解决您的问题。如果您需要更多说明,请回复。


3

Shaunti Fondrisi提供的解决方案几乎是完美的。但这样的一段代码或类似代码的排队执行UICollectionViewreloadData()NSOperationQueuemainQueue确实是把执行时间,以在运行循环的下一个事件循环,这可能使年初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实例只有一个实例NSThreadNSRunLoop完全驱动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的任务调度程序已经存在于我个人使用的框架之一中:Nest。这是其在GitHub上的存储库:https//github.com/WeZZard/Nest


2
 dispatch_async(dispatch_get_main_queue(), ^{

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


        });

它为我工作。


1

首先感谢这个线程,非常有帮助。我的重载数据有一个类似的问题,除了症状是无法永久选择特定单元,而其他单元却可以。没有调用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];`

0

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


您如何更新该计数。.以上所有方法均未迅速为我工作
。– nyxee

0

您设置UICollectionView.contentInset吗?删除左右edgeInset,删除它们后一切正常,该错误仍存在于iOS8.3中。


0

检查每个UICollectionView Delegate方法是否都能实现您期望的效果。例如,如果

collectionView:layout:sizeForItemAtIndexPath:

没有返回有效的大小,重新加载将无法进行...


0

试试这个代码。

 NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];

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

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
}

-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];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.