簡體   English   中英

UICollectionView reloadData 在 iOS 7 中無法正常運行

[英]UICollectionView reloadData not functioning properly in iOS 7

我一直在更新我的應用程序以在 iOS 7 上運行,這在大多數情況下進展順利。 我在不止一個應用程序中注意到UICollectionViewControllerreloadData方法UICollectionViewController那樣起作用。

我將加載UICollectionViewController ,像UICollectionView用一些數據填充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 遇到了同樣的問題。經過長時間的調試會話,我發現了問題。

在 iOS7 上, UICollectionView 上的 reloadData 不會取消尚未完成的先前更新(在 performBatchUpdates: 塊中調用的更新)。

解決此錯誤的最佳解決方案是停止當前正在處理的所有更新並調用 reloadData。 我沒有找到取消或停止一塊 performBatchUpdates 的方法。 因此,為了解決這個錯誤,我保存了一個標志,指示當前是否有一個 performBatchUpdates 塊正在處理。 如果沒有當前處理的更新塊,我可以立即調用 reloadData 並且一切都按預期工作。 如果有當前正在處理的更新塊,我將在 performBatchUpdates 的完整塊上調用 reloadData。

斯威夫特 5 – 4 – 3

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

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

斯威夫特 2

// 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 給出的解決方案幾乎是完美的。 但是這樣一段代碼或類似將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可以在下一個事件循環剛剛開始時觀察到。

由於每個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 的任務調度器已經出現在我個人使用的框架之一: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];`

斯威夫特 5

對我來說,使用所有可見項目調用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM