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