Aktualizowałem swoje aplikacje, aby działały na iOS 7, który w większości przebiega płynnie. Zauważyłem w więcej niż jednej aplikacji, że reloadData
metoda a UICollectionViewController
nie działa tak, jak kiedyś.
Załaduję UICollectionViewController
, wypełnię UICollectionView
danymi jak zwykle. Działa to świetnie za pierwszym razem. Jeśli jednak zażądam nowych danych (wypełnię pole UICollectionViewDataSource
), a następnie wywołam reloadData
, zapyta źródło danych o numberOfItemsInSection
i numberOfSectionsInCollectionView
, ale nie wydaje się, aby wywoływać cellForItemAtIndexPath
odpowiednią liczbę razy.
Jeśli zmienię kod, aby przeładować tylko jedną sekcję, będzie działać poprawnie. Nie mam problemu z ich zmianą, ale nie sądzę, żebym musiał. reloadData
powinien przeładować wszystkie widoczne komórki zgodnie z dokumentacją.
Czy ktoś jeszcze to widział?
źródło
reloadData
po wyświetleniu viewDidAppear wydaje się rozwiązać problem, jego okropne obejście i wymaga naprawy. Mam nadzieję, że ktoś tu pomoże.Odpowiedzi:
Wymuś to w głównym wątku:
dispatch_async(dispatch_get_main_queue(), ^ { [self.collectionView reloadData]; });
źródło
W moim przypadku liczba komórek / sekcji w źródle danych nigdy się nie zmieniła i chciałem tylko przeładować widoczną zawartość na ekranie.
Udało mi się to obejść dzwoniąc:
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
następnie:
[self.collectionView reloadData];
źródło
performBatchUpdates:completion:
bloku?Miałem dokładnie ten sam problem, ale udało mi się znaleźć przyczynę problemu. W moim przypadku wywołałem reloadData z collectionView: cellForItemAtIndexPath: co wygląda na niepoprawne.
Wysłanie wywołania reloadData do głównej kolejki rozwiązało problem raz na zawsze.
dispatch_async(dispatch_get_main_queue(), ^{ [self.collectionView reloadData]; });
źródło
reloadData
został wezwany przez obserwatora zmian.collectionView(_:willDisplayCell:forItemAtIndexPath:)
Ponowne ładowanie niektórych elementów nie zadziałało. W moim przypadku i tylko dlatego, że collectionView, której używam, ma tylko jedną sekcję, po prostu przeładowuję tę konkretną sekcję. Tym razem zawartość jest poprawnie ładowana ponownie. Dziwne, że dzieje się to tylko na iOS 7 (7.0.3)
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
źródło
Miałem ten sam problem z reloadData na iOS 7. Po długiej sesji debugowania znalazłem problem.
W systemie iOS7 reloadData w UICollectionView nie anuluje poprzednich aktualizacji, które jeszcze się nie zakończyły (aktualizacje, które wywołały wewnątrz performBatchUpdates: block).
Najlepszym rozwiązaniem tego błędu jest zatrzymanie wszystkich aktualnie przetwarzanych aktualizacji i wywołanie reloadData. Nie znalazłem sposobu na anulowanie lub zatrzymanie bloku performBatchUpdates. Dlatego, aby rozwiązać błąd, zapisałem flagę, która wskazuje, czy istnieje blok performBatchUpdates, który jest obecnie przetwarzany. Jeśli nie ma aktualnie przetwarzanego bloku aktualizacji, mogę natychmiast wywołać reloadData i wszystko działa zgodnie z oczekiwaniami. Jeśli istnieje blok aktualizacji, który jest obecnie przetwarzany, wywołam reloadData na całym bloku performBatchUpdates.
źródło
Szybki 5-4-3
// GCD DispatchQueue.main.async(execute: collectionView.reloadData) // Operation OperationQueue.main.addOperation(collectionView.reloadData)
Szybki 2
// Operation NSOperationQueue.mainQueue().addOperationWithBlock(collectionView.reloadData)
źródło
Ja też miałem ten problem. Przez przypadek dodałem przycisk na górze widoku kolekcji, aby wymusić ponowne załadowanie w celu przetestowania - i nagle zaczęły się wywoływać metody.
Po prostu dodanie czegoś tak prostego, jak
UIView *aView = [UIView new]; [collectionView addSubView:aView];
spowodowałoby wywołanie metod
Bawiłem się też rozmiarem klatki - i voila, metody były wywoływane.
Istnieje wiele błędów związanych z UICollectionView w iOS7.
źródło
Możesz użyć tej metody
[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];
Możesz dodać wszystkie
indexPath
obiektyUICollectionView
do tablicy,arrayOfAllIndexPaths
wykonując iterację pętli dla wszystkich sekcji i wierszy za pomocą poniższej metody[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];
Mam nadzieję, że zrozumiałeś i może rozwiązać twój problem. Jeśli potrzebujesz więcej wyjaśnień, odpowiedz.
źródło
Rozwiązanie podane przez Shaunti Fondrisi jest prawie doskonałe. Ale taki fragment kodu lub kody, takie jak umieszczanie w kolejce wykonywania poleceń
UICollectionView
'sreloadData()
toNSOperationQueue
', wmainQueue
rzeczywistości umieszcza czas wykonania na początku następnej pętli zdarzenia w pętli uruchamiania, co może spowodować, żeUICollectionView
aktualizację jednym ruchem.Aby rozwiązać ten problem. Musimy umieścić czas wykonania tego samego fragmentu kodu na końcu bieżącej pętli zdarzeń, ale nie na początku następnego. Możemy to osiągnąć, korzystając z
CFRunLoopObserver
.CFRunLoopObserver
obserwuje wszystkie oczekujące działania źródła wejściowego oraz działania wejścia i wyjścia pętli uruchamiania.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 } }
Wśród tych działań
.AfterWaiting
można zaobserwować, kiedy kończy się bieżąca pętla zdarzeń, oraz.BeforeWaiting
można zaobserwować, gdy następna pętla zdarzeń właśnie się rozpoczęła.Ponieważ istnieje tylko jedna
NSRunLoop
instancja naNSThread
iNSRunLoop
dokładnie napędówNSThread
, możemy uznać, że dostęp pochodzi z tego samegoNSRunLoop
instancji, nigdy nie przechodząc przez wątki.W oparciu o punkty wymienione wcześniej, możemy teraz napisać kod: dyspozytor zadań oparty na 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 } } }
Z kodem wcześniej, możemy teraz wysyłką wykonanie
UICollectionView
„sreloadData()
do końca obecnej pętli zdarzeń przez taki fragment kodu:NSRunLoop.currentRunLoop().perform({ () -> Void in collectionView.reloadData() }).when(.CurrentLoopEnded)
W rzeczywistości taki dyspozytor zadań oparty na NSRunLoop był już w jednym z moich osobistych używanych frameworków: Nest. A oto jego repozytorium na GitHub: https://github.com/WeZZard/Nest
źródło
dispatch_async(dispatch_get_main_queue(), ^{ [collectionView reloadData]; [collectionView layoutIfNeeded]; [collectionView reloadData]; });
to działało dla mnie.
źródło
Przede wszystkim dziękuję za ten wątek, bardzo pomocny. Miałem podobny problem z Reload Data, z wyjątkiem tego, że objawem było to, że określonych komórek nie można było już wybierać na stałe, podczas gdy inne mogły. Brak wywołania metody indexPathsForSelectedItems lub jej odpowiednika. Debugowanie wskazało na ponowne załadowanie danych. Wypróbowałem obie powyższe opcje; i zakończyło się przyjęciem opcji ReloadItemsAtIndexPaths, ponieważ inne opcje nie działały w moim przypadku lub powodowały, że widok kolekcji migał przez milisekundę. Poniższy kod działa dobrze:
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];`
źródło
Zdarzyło mi się to również w sdk iOS 8.1, ale poprawiłem się, gdy zauważyłem, że nawet po aktualizacji
datasource
metodynumberOfItemsInSection:
nie zwracała nowej liczby elementów. Zaktualizowałem liczbę i uruchomiłem.źródło
Czy ustawiasz UICollectionView.contentInset? usuń lewą i prawą krawędźInset, wszystko jest w porządku po ich usunięciu, błąd nadal istnieje w iOS8.3.
źródło
Sprawdź, czy każda z metod delegata UICollectionView robi to, czego oczekujesz. Na przykład, jeśli
collectionView:layout:sizeForItemAtIndexPath:
nie zwraca prawidłowego rozmiaru, przeładowanie nie zadziała ...
źródło
wypróbuj ten kod.
NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems]; if (visibleIdx.count) { [self.collectionView reloadItemsAtIndexPaths:visibleIdx]; }
źródło
Oto jak to zadziałało w 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 }
źródło
inservif (isInsertHead) { [self insertItemsAtIndexPaths:tmpPoolIndex]; NSArray * visibleIdx = [self indexPathsForVisibleItems]; if (visibleIdx.count) { [self reloadItemsAtIndexPaths:visibleIdx]; } }else if (isFirstSyncData) { [self reloadData]; }else{ [self insertItemsAtIndexPaths:tmpPoolIndex]; }
źródło