UICollectionView reloadData no funciona correctamente en iOS 7
ios7 (17)
Swift 4 - 3
// GCD
DispatchQueue.main.async(execute: collectionView.reloadData)
// Operation
OperationQueue.main.addOperation(collectionView.reloadData)
Swift 2
// Operation
NSOperationQueue.mainQueue().addOperationWithBlock(collectionView.reloadData)
He estado actualizando mis aplicaciones para ejecutarlas en iOS 7, lo que está funcionando sin problemas en su mayor parte. Me he dado cuenta en más de una aplicación que el método reloadData
de un UICollectionViewController
no está actuando como solía hacerlo.
UICollectionViewController
, completaré el UICollectionView
con algunos datos de forma normal. Esto funciona genial en la primera vez. Sin embargo, si solicito nuevos datos ( UICollectionViewDataSource
el UICollectionViewDataSource
) y luego reloadData
, consultará el origen de datos para numberOfItemsInSection
y numberOfSectionsInCollectionView
, pero no parece llamar a cellForItemAtIndexPath
el número de veces adecuado.
Si cambio el código solo para volver a cargar una sección, funcionará correctamente. Este no es un problema para mí para cambiar esto, pero no creo que deba hacerlo. reloadData
debe volver a cargar todas las celdas visibles de acuerdo con la documentación.
¿Alguien más ha visto esto?
¿Estableces UICollectionView.contentInset? elimine los bordes izquierdo y derecho, todo está bien después de que los elimine, el error aún existe en iOS8.3.
Así es como me funcionó en 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
}
Compruebe que cada uno de los métodos de delegado UICollectionView haga lo que espera que haga. Por ejemplo, si
collectionView:layout:sizeForItemAtIndexPath:
no devuelve un tamaño válido, la recarga no funcionará ...
En mi caso, el número de células / secciones en el origen de datos nunca cambió y solo quería volver a cargar el contenido visible en la pantalla.
Logré solucionar esto llamando:
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
entonces:
[self.collectionView reloadData];
Forzar esto en el hilo principal:
dispatch_async(dispatch_get_main_queue(), ^ {
[self.collectionView reloadData];
});
Gracias en primer lugar por este hilo, muy útil. Tuve un problema similar con Reload Data, excepto que el síntoma era que las células específicas ya no podían seleccionarse de forma permanente, mientras que otras sí podían. Sin llamada al método indexPathsForSelectedItems o equivalente. Depuración apuntada a Reload Data. Intenté ambas opciones arriba; y terminé adoptando la opción ReloadItemsAtIndexPaths ya que las otras opciones no funcionaron en mi caso o hacían que la vista de la colección parpadeara durante un milisegundo más o menos. El siguiente código funciona bien:
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];`
La solución dada por Shaunti Fondrisi es casi perfecta. Pero una pieza de código o códigos como enqueue la ejecución de UICollectionView
reloadData()
de NSOperationQueue
a NSOperationQueue
de mainQueue
hecho pone el tiempo de ejecución al comienzo del próximo ciclo de eventos en el ciclo de ejecución, lo que podría hacer que la actualización de UICollectionView
con un película.
Para resolver este problema Debemos poner el tiempo de ejecución de la misma pieza de código al final del ciclo de evento actual pero no al principio de la siguiente. Y podemos lograr esto haciendo uso de CFRunLoopObserver
.
CFRunLoopObserver
observa todas las actividades de espera de la fuente de entrada y la actividad de entrada y salida del bucle de ejecución.
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 }
}
Entre esas actividades, se puede observar. .AfterWaiting
cuando el ciclo de evento actual está a punto de terminar, y .BeforeWaiting
se puede observar cuando el siguiente ciclo de evento acaba de comenzar.
Como solo hay una instancia de NSRunLoop
por cada NSThread
y NSRunLoop
controla exactamente el NSThread
, podemos considerar que los accesos provenientes de la misma instancia de NSRunLoop
nunca cruzan los hilos.
En función de los puntos mencionados anteriormente, ahora podemos escribir el código: un despachador de tareas basado en 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
}
}
}
Con el código anterior, ahora podemos enviar la ejecución de UICollectionView
reloadData()
al final del bucle de evento actual mediante un fragmento de código:
NSRunLoop.currentRunLoop().perform({ () -> Void in
collectionView.reloadData()
}).when(.CurrentLoopEnded)
De hecho, un despachador de tareas basado en NSRunLoop ya ha estado en uno de mis marcos de trabajo personales: Nest. Y aquí está su repositorio en GitHub: https://github.com/WeZZard/Nest
Me pasó a mí también en iOS 8.1 sdk, pero lo entendí correctamente cuando noté que incluso después de actualizar el datasource
el método numberOfItemsInSection:
no devolvía el nuevo recuento de elementos. Actualicé el conteo y lo puse en funcionamiento.
Puedes usar este método
[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];
Puede agregar todos los objetos indexPath
de su UICollectionView
en array arrayOfAllIndexPaths
iterando el ciclo para todas las secciones y filas con el uso del siguiente método
[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];
Espero que lo haya entendido y que pueda resolver su problema. Si necesita más explicaciones, responda.
Recargar algunos artículos no funcionó para mí. En mi caso, y solo porque el collectionView que estoy usando tiene solo una sección, simplemente recargo esa sección en particular. Esta vez, los contenidos se vuelven a cargar correctamente. Es extraño que esto solo ocurra en iOS 7 (7.0.3)
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
Tenía exactamente el mismo problema, sin embargo, logré encontrar lo que estaba pasando mal. En mi caso, estaba llamando a reloadData desde collectionView: cellForItemAtIndexPath: que parece no ser correcto.
El envío de call de reloadData a la cola principal solucionó el problema de una vez y para siempre.
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
Tuve el mismo problema con reloadData en iOS 7. Después de una larga sesión de depuración, encontré el problema.
En iOS7, reloadData en UICollectionView no cancela las actualizaciones anteriores que aún no se han completado (las actualizaciones que llaman dentro de performBatchUpdates: bloque).
La mejor solución para resolver este error es detener todas las actualizaciones que actualmente se procesan y llamar a reloadData. No encontré la forma de cancelar o detener un bloque de performBatchUpdates. Por lo tanto, para resolver el error, guardé una bandera que indica si hay un bloque performBatchUpdates que se procesa actualmente. Si no hay un bloque de actualización procesado actualmente, puedo llamar a reloadData inmediatamente y todo funciona como se espera. Si hay un bloque de actualización que se procesa actualmente, llamaré a reloadData en el bloque completo de performBatchUpdates.
Yo también tuve este problema. Por casualidad, agregué un botón en la vista de colección para forzar la recarga para probar y, de repente, los métodos comenzaron a llamarse.
También solo agregar algo tan simple como
UIView *aView = [UIView new];
[collectionView addSubView:aView];
causaría que los métodos sean llamados
También jugué con el tamaño de fotograma y voila los métodos fueron llamados.
Hay muchos errores con iOS7 UICollectionView.
prueba este código
NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];
if (visibleIdx.count) {
[self.collectionView reloadItemsAtIndexPaths:visibleIdx];
}
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView reloadData];
[collectionView layoutIfNeeded];
[collectionView reloadData];
});
funcionó para mí.
inservif (isInsertHead) {
[self insertItemsAtIndexPaths:tmpPoolIndex];
NSArray * visibleIdx = [self indexPathsForVisibleItems];
if (visibleIdx.count) {
[self reloadItemsAtIndexPaths:visibleIdx];
}
}else if (isFirstSyncData) {
[self reloadData];
}else{
[self insertItemsAtIndexPaths:tmpPoolIndex];
}