UICollectionView reloadData не работает должным образом в iOS 7

Я обновляю свои приложения для работы на iOS 7, который работает гладко по большей части. В нескольких приложениях я заметил, что метод reloadData UICollectionViewController не действует так, как он привык.

Я загружу UICollectionViewController, заполнив UICollectionView некоторыми данными как обычно. Это отлично работает в первый раз. Однако, если я запрашиваю новые данные (заполняю UICollectionViewDataSource), а затем вызываю reloadData, он будет запрашивать источник данных для numberOfItemsInSection и numberOfSectionsInCollectionView, но, похоже, он не вызывает cellForItemAtIndexPath правильный номер раз.

Если я изменю код, чтобы перезагрузить только один раздел, он будет функционировать должным образом. Для меня это не проблема, чтобы изменить их, но я не думаю, что должен. reloadData должен перезагрузить все видимые ячейки в соответствии с документацией.

Кто-нибудь еще видел это?

Ответ 1

Настроить это на основной поток:

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

Ответ 2

В моем случае количество ячеек/секций в источнике данных никогда не менялось, и я просто хотел перезагрузить видимый контент на экране.

Мне удалось обойти это, позвонив:

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

тогда:

[self.collectionView reloadData];

Ответ 3

У меня была точно такая же проблема, однако мне удалось найти, что происходит не так. В моем случае я вызывал reloadData​​strong > из коллекции: cellForItemAtIndexPath:, которая выглядит неправильно.

Диспетчерский вызов reloadData​​strong > в основную очередь исправил проблему один раз и навсегда.

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

Ответ 4

Перезагрузка некоторых предметов не работала для меня. В моем случае, и только потому, что collectionView, который я использую, имеет только один раздел, я просто перезаряжаю этот конкретный раздел. На этот раз содержимое будет правильно перезагружено. Странно, что это происходит только на iOS 7 (7.0.3)

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

Ответ 5

Свифт 5 - 4 - 3

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

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

Swift 2

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

Ответ 6

У меня была такая же проблема с reloadData на iOS 7. После долгого отладочного сеанса я нашел проблему.

В iOS7 reloadData в UICollectionView не отменяет предыдущие обновления, которые еще не завершены (Обновления, вызываемые внутри executeBatchUpdates: block).

Лучшим решением для решения этой проблемы является остановка всех текущих обновлений и вызов reloadData. Я не нашел способ отменить или остановить блок performBatchUpdates. Поэтому, чтобы решить проблему, я сохранил флаг, который указывает, есть ли исполняемый блок executeBatchUpdates, который в настоящее время обрабатывается. Если в настоящее время не выполняется блок обновления, я могу немедленно вызвать reloadData, и все работает должным образом. Если есть блок обновления, который в настоящее время обрабатывается, я вызову reloadData в полный блок executeBatchUpdates.

Ответ 7

У меня также была эта проблема. По совпадению я добавил кнопку поверх коллекции, чтобы принудительно перезагрузить для тестирования - и внезапно методы начали называться.

Также просто добавьте что-то простое, как

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

приведет к тому, что методы будут называться

Также я играл с размером кадра - и вуаля вызывали методы.

В iOS7 UICollectionView есть много ошибок.

Ответ 8

Вы можете использовать этот метод

[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];

Вы можете добавить все indexPath объекты вашего UICollectionView в массив arrayOfAllIndexPaths, итерации цикла для всех разделов и строк с использованием метода ниже

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

Надеюсь, вы поняли, и это может решить вашу проблему. Если вам нужно больше объяснений, ответьте.

Ответ 9

Решение, данное Шаунти Фондриси, почти идеально. Но такой фрагмент кода или кодов, например enqueue, выполняет 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 всегда никогда не пересекают потоки.

Основываясь на упомянутых выше моментах, мы можем теперь написать код: диспетчер задач на основе 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 reloadData() в конец текущего цикла событий таким фрагментом кода:

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

Фактически, такой диспетчер задач, основанный на NSRunLoop, уже был в моей личной используемой структуре: Nest. И вот его репозиторий на GitHub: https://github.com/WeZZard/Nest

Ответ 10

Спасибо в первую очередь за эту тему, очень полезно. У меня была аналогичная проблема с 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

Это случилось со мной тоже в iOS 8.1 sdk, но я понял это правильно, когда заметил, что даже после обновления datasource метод numberOfItemsInSection: не возвращал новое количество элементов. Я обновил счет и получил его работу.

Ответ 12

Вы устанавливаете UICollectionView.contentInset? удалите левый и правый крайInset, все будет хорошо после того, как я их удалю, ошибка все еще существует в iOS8.3.

Ответ 13

Убедитесь, что каждый из методов делегата UICollectionView выполняет то, что вы ожидаете от него. Например, если

collectionView:layout:sizeForItemAtIndexPath:

не возвращает допустимый размер, перезагрузка не будет работать...

Ответ 14

попробуйте этот код.

 NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];

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

Ответ 15

 dispatch_async(dispatch_get_main_queue(), ^{

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


        });

это сработало для меня.

Ответ 16

Вот как это работает для меня в 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

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