Я обновляю свои приложения для работы на iOS 7, что по большей части проходит гладко. Я заметил в более чем одном приложении, что reloadData
метод a UICollectionViewController
работает не совсем так, как раньше.
Я загружу UICollectionViewController
, заполню UICollectionView
некоторыми данными как обычно. Это отлично работает в первый раз. Однако, если я запрашиваю новые данные (заполняю UICollectionViewDataSource
), а затем вызываю reloadData
, он запрашивает у источника данных numberOfItemsInSection
и numberOfSectionsInCollectionView
, но, похоже, не вызывает cellForItemAtIndexPath
нужное количество раз.
Если я изменю код, чтобы перезагрузить только один раздел, он будет работать правильно. Для меня нетрудно изменить их, но я не думаю, что мне следовало бы это делать. reloadData
должен перезагрузить все видимые ячейки согласно документации.
Кто-нибудь еще видел это?
источник
reloadData
после viewDidAppear, похоже, решает проблему, это ужасный обходной путь и требует исправления. Надеюсь, здесь кто-то поможет.Ответы:
Заставьте это в основном потоке:
dispatch_async(dispatch_get_main_queue(), ^ { [self.collectionView reloadData]; });
источник
В моем случае количество ячеек / разделов в источнике данных никогда не менялось, и я просто хотел перезагрузить видимый контент на экране ..
Мне удалось обойти это, позвонив:
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
тогда:
[self.collectionView reloadData];
источник
performBatchUpdates:completion:
блок?У меня была точно такая же проблема, однако мне удалось выяснить, что происходит не так. В моем случае я вызывал reloadData из collectionView: cellForItemAtIndexPath: что, похоже, неверно.
Отправка вызова reloadData в основную очередь решила проблему раз и навсегда.
dispatch_async(dispatch_get_main_queue(), ^{ [self.collectionView reloadData]; });
источник
reloadData
вызвал наблюдатель изменений.collectionView(_:willDisplayCell:forItemAtIndexPath:)
Перезагрузка некоторых предметов у меня не сработала. В моем случае, и только потому, что в collectionView, который я использую, есть только один раздел, я просто перезагружаю этот конкретный раздел. На этот раз содержимое перезагружено правильно. Странно, что это происходит только на iOS 7 (7.0.3)
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
источник
У меня была такая же проблема с reloadData на iOS 7. После долгого сеанса отладки я обнаружил проблему.
В iOS7 reloadData в UICollectionView не отменяет предыдущие обновления, которые еще не завершены (обновления, которые вызываются внутри блока performBatchUpdates:).
Лучшее решение для устранения этой ошибки - остановить все обновления, которые в настоящее время обрабатываются, и вызвать reloadData. Я не нашел способа отменить или остановить блок performBatchUpdates. Поэтому, чтобы исправить ошибку, я сохранил флаг, который указывает, есть ли блок performBatchUpdates, который в данный момент обрабатывается. Если нет блока обновления, который в настоящее время обрабатывается, я могу немедленно вызвать reloadData, и все будет работать, как ожидалось. Если есть блок обновления, который в настоящее время обрабатывается, я вызываю reloadData для всего блока performBatchUpdates.
источник
Swift 5 - 4 - 3
// GCD DispatchQueue.main.async(execute: collectionView.reloadData) // Operation OperationQueue.main.addOperation(collectionView.reloadData)
Swift 2
// Operation NSOperationQueue.mainQueue().addOperationWithBlock(collectionView.reloadData)
источник
У меня тоже была такая проблема. По совпадению я добавил кнопку в верхней части коллекции, чтобы принудительно перезагрузить для тестирования - и внезапно методы начали вызываться.
Также просто добавляем что-то простое, как
UIView *aView = [UIView new]; [collectionView addSubView:aView];
вызовет методы
Также я поигрался с размером кадра - и вуаля вызывались методы.
В iOS7 UICollectionView много ошибок.
источник
Вы можете использовать этот метод
[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];
Вы можете добавить все свои
indexPath
объектыUICollectionView
в массивarrayOfAllIndexPaths
, повторяя цикл для всех разделов и строк с использованием метода ниже[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];
Надеюсь, вы поняли, и это может решить вашу проблему. Если вам нужны дополнительные объяснения, ответьте.
источник
Решение, данное Шаунти Фондриси, почти идеальное. Но такой фрагмент кода или такие коды, как постановка в очередь выполнения
UICollectionView
'sreloadData()
toNSOperationQueue
',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
'sreloadData()
в конец текущего цикла событий с помощью такого фрагмента кода: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];`
источник
Это случилось со мной и в iOS 8.1 sdk, но я понял все правильно, когда заметил, что даже после обновления
datasource
методаnumberOfItemsInSection:
не возвращалось новое количество элементов. Я обновил счетчик, и он заработал.источник
Вы устанавливаете UICollectionView.contentInset? удалите левый и правый крайInset, после удаления все в порядке, ошибка все еще существует в iOS8.3.
источник
Убедитесь, что каждый из методов делегата UICollectionView выполняет то, что вы от него ожидаете. Например, если
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]; }
источник