web-dev-qa-db-fra.com

UICollectionView reloadData ne fonctionne pas correctement sous iOS 7

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?

93
VaporwareWolf

Force ceci sur le fil principal:

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

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];
63
liamnichols

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];
  });
26
Anton Matosov

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]];
19
miguelsanchez

Swift 4 - 3

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

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

Swift 2

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

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.

11
user2459624

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.

4
Avner Barr

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

3
WeZZard

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.

3
iDevAmit

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];`
1
stephane

essayez ce code.

 NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];

    if (visibleIdx.count) {
        [self.collectionView reloadItemsAtIndexPaths:visibleIdx];
    }
0
Liki qu
 dispatch_async(dispatch_get_main_queue(), ^{

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


        });

cela a fonctionné pour moi.

0
Prajakta

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.

0
Jiang Qi

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 ...

0
Oded Regev

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
}
0
Wissa

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.

0
Vinay Jain