简体   繁体   English

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

[英]UICollectionView reloadData not functioning properly in iOS 7

I've been updating my apps to run on iOS 7 which is going smoothly for the most part.我一直在更新我的应用程序以在 iOS 7 上运行,这在大多数情况下进展顺利。 I have noticed in more than one app that the reloadData method of a UICollectionViewController isn't acting quite how it used to.我在不止一个应用程序中注意到UICollectionViewControllerreloadData方法UICollectionViewController那样起作用。

I'll load the UICollectionViewController , populate the UICollectionView with some data as normal.我将加载UICollectionViewController ,像UICollectionView用一些数据填充UICollectionView This works great on the first time.这在第一次很有效。 However if I request new data (populate the UICollectionViewDataSource ), and then call reloadData , it will query the data source for numberOfItemsInSection and numberOfSectionsInCollectionView , but it doesn't seem to call cellForItemAtIndexPath the proper number of times.但是,如果我请求新数据(填充UICollectionViewDataSource ),然后调用reloadData ,它将查询numberOfItemsInSectionnumberOfSectionsInCollectionView的数据源,但它似乎没有调用cellForItemAtIndexPath正确的次数。

If I change the code to only reload one section, then it will function properly.如果我将代码更改为仅重新加载一个部分,那么它将正常运行。 This is no problem for me to change these, but I don't think I should have to.改变这些对我来说没有问题,但我认为我不应该这样做。 reloadData should reload all visible cells according to the documentation. reloadData应根据文档重新加载所有可见单元格。

Has anyone else seen this?有没有其他人看到这个?

Force this on the main thread:在主线程上强制执行此操作:

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

In my case, the number of cells/sections in the datasource never changed and I just wanted to reload the visible content on the screen..就我而言,数据源中单元格/部分的数量从未改变,我只想重新加载屏幕上的可见内容..

I managed to get around this by calling:我设法通过调用来解决这个问题:

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

then:然后:

[self.collectionView reloadData];

I had exactly the same issue, however I managed to find what was going on wrong.我遇到了完全相同的问题,但是我设法找到了问题所在。 In my case I was calling reloadData from the collectionView:cellForItemAtIndexPath: which looks not to be correct.在我的情况下,我从collectionView:cellForItemAtIndexPath:调用reloadData :这看起来不正确。

Dispatching call of reloadData to the main queue fixed the problem once and forever.reloadData 的调用分派到主队列一劳永逸地解决了这个问题。

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

Reloading some items didn't work for me.重新加载一些项目对我不起作用。 In my case, and only because the collectionView I'm using has just one section, I simply reload that particular section.就我而言,仅因为我使用的 collectionView 只有一个部分,我只需重新加载该特定部分。 This time the contents are correctly reloaded.这次内容正确地重新加载。 Weird that this is only happening on iOS 7 (7.0.3)奇怪的是,这仅发生在 iOS 7 (7.0.3) 上

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

I had the same issue with reloadData on iOS 7. After long debug session, I found the problem.我在 iOS 7 上的 reloadData 遇到了同样的问题。经过长时间的调试会话,我发现了问题。

On iOS7, reloadData on UICollectionView doesn't cancel previous updates which haven't completed yet (Updates which called inside performBatchUpdates: block).在 iOS7 上, UICollectionView 上的 reloadData 不会取消尚未完成的先前更新(在 performBatchUpdates: 块中调用的更新)。

The best solution to solve this bug, is stopping all updates which currently processed and call reloadData.解决此错误的最佳解决方案是停止当前正在处理的所有更新并调用 reloadData。 I didn't find a way to cancel or stop a block of performBatchUpdates.我没有找到取消或停止一块 performBatchUpdates 的方法。 Therefore, to solve the bug, I saved a flag which indicates if there's a performBatchUpdates block which currently processed.因此,为了解决这个错误,我保存了一个标志,指示当前是否有一个 performBatchUpdates 块正在处理。 If there isn't an update block which currently processed, I can call reloadData immediately and everything work as expected.如果没有当前处理的更新块,我可以立即调用 reloadData 并且一切都按预期工作。 If there's an update block which currently processed, I'll call reloadData on the complete block of performBatchUpdates.如果有当前正在处理的更新块,我将在 performBatchUpdates 的完整块上调用 reloadData。

Swift 5 – 4 – 3斯威夫特 5 – 4 – 3

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

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

Swift 2斯威夫特 2

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

I also had this problem.我也有这个问题。 By coincidence I added a button on top of the collectionview in order to force reloading for testing - and all of a sudden the methods started getting called.巧合的是,我在 collectionview 顶部添加了一个按钮,以强制重新加载进行测试 - 突然间这些方法开始被调用。

Also just adding something as simple as也只是添加一些简单的东西

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

would cause the methods to be called会导致方法被调用

Also I played around with the frame size - and voila the methods were getting called.我还尝试了帧大小 - 瞧,这些方法被调用了。

There are a lot of bugs with iOS7 UICollectionView. iOS7 UICollectionView 有很多错误。

You can use this method你可以使用这个方法

[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];

You can add all indexPath objects of your UICollectionView into array arrayOfAllIndexPaths by iterating the loop for all sections and rows with use of below method您可以通过使用以下方法迭代所有部分和行的循环,将UICollectionView所有indexPath对象添加到数组arrayOfAllIndexPaths

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

I hope you understood and it can resolve your problem.我希望你理解,它可以解决你的问题。 If you need any more explanation please reply.如果您需要更多解释,请回复。

The solution given by Shaunti Fondrisi is nearly perfect. Shaunti Fondrisi 给出的解决方案几乎是完美的。 But such a piece of code or codes like enqueue the execution of UICollectionView 's reloadData() to NSOperationQueue 's mainQueue indeed puts the execution timing to the beginning of the next event loop in the run loop, which could make the UICollectionView update with a flick.但是这样一段代码或类似将UICollectionViewreloadData()的执行排队到NSOperationQueuemainQueue确实将执行时间置于运行循环中下一个事件循环的开始,这可能会使UICollectionView更新为轻弹。

To solve this issue.为了解决这个问题。 We must put the execution timing of the same piece of code to the end of current event loop but not the beginning of the next.我们必须将同一段代码的执行时间放在当前事件循环的末尾,而不是下一个事件循环的开始。 And we can achieve this by making use of CFRunLoopObserver .我们可以通过使用CFRunLoopObserver来实现这一点。

CFRunLoopObserver observes all the input source waiting activities and the run loop's entry and exit activity. 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 }
}

Among those activities, .AfterWaiting can be observed when current event loop is about to end, and .BeforeWaiting can be observed when the next event loop just has began.在这些活动中, .AfterWaiting可以在当前事件循环即将结束时观察到, .BeforeWaiting可以在下一个事件循环刚刚开始时观察到。

As there is only one NSRunLoop instance per NSThread and NSRunLoop exactly drives the NSThread , we can consider that accesses come from the same NSRunLoop instance always never cross threads.由于每个NSThread只有一个NSRunLoop实例并且NSRunLoop正好驱动NSThread ,我们可以认为来自同一个NSRunLoop实例的访问永远不会跨线程。

Based on points mentioned before, we can now write the code: an NSRunLoop-based task dispatcher:基于之前提到的几点,我们现在可以编写代码:一个基于 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
        }
    }
}

With the code before, we can now dispatch the execution of UICollectionView 's reloadData() to the end of current event loop by such a piece of code:使用之前的代码,我们现在可以通过这样一段代码将UICollectionViewreloadData()的执行分派到当前事件循环的末尾:

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

In fact, such an NSRunLoop based task dispatcher has already been in one of my personal used framework: Nest.事实上,这样一个基于 NSRunLoop 的任务调度器已经出现在我个人使用的框架之一:Nest 中。 And here is its repository on GitHub: https://github.com/WeZZard/Nest这是它在 GitHub 上的存储库: https : //github.com/WeZZard/Nest

 dispatch_async(dispatch_get_main_queue(), ^{

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


        });

it worked for me.它对我有用。

Thanks first of all for this thread, very helpful.首先感谢这个线程,非常有帮助。 I had a similar issue with Reload Data except the symptom was that specific cells could no longer be selected in a permanent way whereas others could.我在重新加载数据时遇到了类似的问题,但症状是无法再以永久方式选择特定单元格,而其他单元格可以。 No call to indexPathsForSelectedItems method or equivalent.不调用 indexPathsForSelectedItems 方法或等效方法。 Debugging pointed out to Reload Data.调试指出重新加载数据。 I tried both options above ;我尝试了上面的两个选项; and ended up adopting the ReloadItemsAtIndexPaths option as the other options didn't work in my case or were making the collection view flash for a milli-second or so.并最终采用了 ReloadItemsAtIndexPaths 选项,因为其他选项在我的情况下不起作用,或者使集合视图闪烁一毫秒左右。 The code below works good:下面的代码效果很好:

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];`

Swift 5斯威夫特 5

For me, calling reloadItems(at:) with all visible items worked for me, instead of reloadData .对我来说,使用所有可见项目调用reloadItems(at:) ,而不是reloadData

collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems)

(Swift version of liamnichols's answer) (liamnichols 的答案的 Swift 版本)

It happened with me too in iOS 8.1 sdk, but I got it correct when I noticed that even after updating the datasource the method numberOfItemsInSection: was not returning the new count of items.我在 iOS 8.1 sdk 中也发生了这种情况,但是当我注意到即使在更新datasource ,方法numberOfItemsInSection:也没有返回新的项目数时,我得到了正确的numberOfItemsInSection: I updated the count and got it working.我更新了计数并让它工作。

Do you set UICollectionView.contentInset?你设置 UICollectionView.contentInset 了吗? remove the left and right edgeInset, everything is ok after I remove them, the bug still exists in iOS8.3 .去掉左右edgeInset,去掉后一切正常,但iOS8.3依然存在bug。

Check that each one of the UICollectionView Delegate methods does what you expect it to do.检查每个 UICollectionView Delegate 方法是否按照您的预期执行。 For example, if例如,如果

collectionView:layout:sizeForItemAtIndexPath:

doesn't return a valid size, the reload won't work...不返回有效大小,重新加载将不起作用...

try this code.试试这个代码。

 NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];

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

Here is how it worked for me in Swift 4这是它在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.

相关问题 UICollectionView ReloadData 未正确显示内容 - UICollectionView ReloadData not showing content properly reloadData不适用于iOS7中的UICollectionView - reloadData not working for UICollectionView in iOS7 iOS UICollectionView reloadData将模型错误地映射到ViewCell - iOS UICollectionView reloadData maps model to ViewCell incorrectly iOS UiCollectionView-使用自定义UiCollectionViewLayout重新加载数据 - iOS UiCollectionView - reloadData using custom UiCollectionViewLayout ios协议无法正常运行 - ios protocol not functioning properly UICollectionView scrollToItemAtIndexPath在导航控制器中无法正常运行 - UICollectionView scrollToItemAtIndexPath not functioning properly in navigation controller iOS 8 Swift UICollectionView reloadData()导致视图(图像)跳转单元格 - iOS 8 Swift UICollectionView reloadData() causing views (images) to jump cells UICollectionView.reloadData()更改单元格顺序| iOS Swift - UICollectionView.reloadData() changes cell order | iOS Swift UITableView 上的 iOS Swift ReloadData 不会刷新 UITableViewCell 内的 UICollectionView - iOS Swift ReloadData on UITableView does not refresh UICollectionView inside of UITableViewCell 在reloadData上禁用UICollectionView动画 - Disable UICollectionView animation on reloadData
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM