J'ai mis à jour mes applications pour qu'elles fonctionnent sous iOS 7, ce qui se passe généralement sans heurts. J'ai remarqué dans plus d'une application que la méthode reloadData
d'une UICollectionViewController
n'agissait pas exactement comme elle le faisait auparavant.
Je vais charger la UICollectionViewController
, peupler la UICollectionView
avec certaines données comme d'habitude. Cela fonctionne très bien la première fois. Cependant, si je demande de nouvelles données (renseignez la UICollectionViewDataSource
), puis appelez reloadData
, il interrogera la source de données pour numberOfItemsInSection
et numberOfSectionsInCollectionView
, mais il ne semble pas appeler cellForItemAtIndexPath
le nombre de fois voulu.
Si je modifie le code pour ne recharger qu'une section, il fonctionnera correctement. Ce n'est pas un problème pour moi de les changer, mais je ne pense pas que je devrais le faire. reloadData
doit recharger toutes les cellules visibles conformément à la documentation.
Quelqu'un d'autre a-t-il vu cela?
Force ceci sur le fil principal:
dispatch_async(dispatch_get_main_queue(), ^ {
[self.collectionView reloadData];
});
Dans mon cas, le nombre de cellules/sections dans la source de données n'a jamais changé et je voulais simplement recharger le contenu visible à l'écran.
J'ai réussi à contourner cela en appelant:
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
puis:
[self.collectionView reloadData];
J'ai eu exactement le même problème, mais j'ai réussi à trouver ce qui se passait mal… .. Dans mon cas, j'appelais reloadData à partir de collectionView: cellForItemAtIndexPath: qui ne semble pas être correct.
L'affectation de l'appel de reloadData à la file d'attente principale a résolu le problème une fois pour toutes.
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
Le rechargement de certains articles n'a pas fonctionné pour moi. Dans mon cas, et uniquement parce que collectionView que j'utilise ne contient qu'une section, je recharge simplement cette section particulière. Cette fois, le contenu est correctement rechargé . Bizarre que cela ne se produise que sur 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)
J'ai eu le même problème avec reloadData sur iOS 7. Après une longue session de débogage, j'ai trouvé le problème.
Sur iOS7, reloadData sur UICollectionView n'annule pas les mises à jour précédentes qui ne sont pas encore terminées (mises à jour appelées dans performBatchUpdates: block).
La meilleure solution pour résoudre ce problème consiste à arrêter toutes les mises à jour en cours de traitement et à appeler reloadData. Je n'ai pas trouvé le moyen d'annuler ou d'arrêter un bloc de performBatchUpdates. Par conséquent, pour résoudre le bogue, j'ai enregistré un indicateur indiquant s'il existe un bloc performBatchUpdates actuellement traité. S'il n'y a pas de bloc de mise à jour actuellement traité, je peux appeler immédiatement reloadData et tout fonctionne comme prévu. Si un bloc de mise à jour est en cours de traitement, j'appellerai reloadData dans le bloc complet de performBatchUpdates.
J'ai aussi eu ce problème. Par coïncidence, j'ai ajouté un bouton au-dessus de la vue de collecte afin de forcer le rechargement à des fins de test.
Aussi juste en ajoutant quelque chose d'aussi simple que
UIView *aView = [UIView new];
[collectionView addSubView:aView];
provoquerait l'appel des méthodes
De plus, j'ai joué avec la taille du cadre - et voilà, les méthodes étaient appelées.
Il y a beaucoup de bugs avec iOS7 UICollectionView.
La solution donnée par Shaunti Fondrisi est presque parfaite. Mais un élément de code tel que mettre en file d'attente l'exécution de reloadData()
de UICollectionView
sur NSOperationQueue
de mainQueue
place effectivement le minutage d'exécution au début de la prochaine boucle d'événements dans la boucle d'exécution, ce qui pourrait mettre à jour le UICollectionView
avec un survol.
Pour résoudre ce problème. Nous devons mettre le temps d'exécution du même morceau de code à la fin de la boucle d'événements en cours, mais pas au début de la suivante. Et nous pouvons y parvenir en utilisant CFRunLoopObserver
.
CFRunLoopObserver
observe toutes les activités en attente de la source d'entrée et l'activité d'entrée et de sortie de la boucle d'analyse.
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 }
}
Parmi ces activités, .AfterWaiting
peut être observé lorsque la boucle d'événement en cours est sur le point de se terminer, et .BeforeWaiting
peut être observé lorsque la prochaine boucle d'événement vient juste de commencer.
Comme il n'y a qu'une seule instance NSRunLoop
par NSThread
et NSRunLoop
conduit exactement le NSThread
, nous pouvons considérer que les accès proviennent de la même instance NSRunLoop
que vous ne rencontrez jamais de threads.
Sur la base des points mentionnés précédemment, nous pouvons maintenant écrire le code: un répartiteur de tâches basé sur 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
}
}
}
Avec le code précédent, nous pouvons maintenant répartir l’exécution de reloadData()
de UICollectionView
à la fin de la boucle d’événement en cours avec un tel code:
NSRunLoop.currentRunLoop().perform({ () -> Void in
collectionView.reloadData()
}).when(.CurrentLoopEnded)
En fait, un tel répartiteur de tâches basé sur NSRunLoop se trouvait déjà dans l'un de mes frameworks personnels: Nest. Et voici son dépôt sur GitHub: https://github.com/WeZZard/Nest
Vous pouvez utiliser cette méthode
[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];
Vous pouvez ajouter tous les objets indexPath
de votre UICollectionView
au tableau arrayOfAllIndexPaths
en itérant la boucle pour toutes les sections et toutes les lignes à l'aide de la méthode below
[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];
J'espère que vous avez compris et que cela peut résoudre votre problème. Si vous avez besoin d'explications supplémentaires, veuillez répondre.
Merci tout d'abord pour ce fil, très utile. J'ai eu un problème similaire avec Reload Data, sauf que le symptôme était que des cellules spécifiques ne pouvaient plus être sélectionnées de manière permanente alors que d'autres le pouvaient. Aucun appel à la méthode indexPathsForSelectedItems ou équivalente. Le débogage a signalé Reload Data. J'ai essayé les deux options ci-dessus; et a fini par adopter l'option ReloadItemsAtIndexPaths car les autres options ne fonctionnaient pas dans mon cas ou faisaient en sorte que la vue de collection clignote pendant un millième de seconde environ. Le code ci-dessous fonctionne 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];`
essayez ce code.
NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];
if (visibleIdx.count) {
[self.collectionView reloadItemsAtIndexPaths:visibleIdx];
}
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView reloadData];
[collectionView layoutIfNeeded];
[collectionView reloadData];
});
cela a fonctionné pour moi.
Définissez-vous UICollectionView.contentInset? supprimer les bords gauche et droit, tout va bien après que je les ai supprimés, le bogue existe toujours dans iOS8.3.
Vérifiez que chacune des méthodes UICollectionView Delegate fait ce que vous attendez de lui. Par exemple, si
collectionView:layout:sizeForItemAtIndexPath:
ne renvoie pas une taille valide, la recharge ne fonctionnera pas ...
Voici comment cela a fonctionné pour moi dans 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
}
Cela m’est aussi arrivé avec iOS 8.1 sdk, mais j’ai eu raison de le constater, même après la mise à jour de la variable datasource
, la méthode numberOfItemsInSection:
ne renvoyait pas le nouveau nombre d’articles. J'ai mis à jour le compte et je l'ai fait fonctionner.