UICollectionView reloadDataがiOS 7で正しく機能しない


93

私はiOS 7で実行するようにアプリを更新してきましたが、これは大部分がスムーズに進んでいます。複数のアプリで、reloadDataa のメソッドがUICollectionViewController以前のように機能していないことに気付きました。

をロードしUICollectionViewControllerUICollectionView通常どおりにデータを入力します。これは初めてうまくいきます。ただし、新しいデータを要求し(に入力UICollectionViewDataSource)、次にを呼び出すとreloadData、データソースにnumberOfItemsInSectionとがクエリさnumberOfSectionsInCollectionViewれますがcellForItemAtIndexPath、適切な回数は呼び出されないようです。

1つのセクションのみを再読み込みするようにコードを変更すると、正しく機能します。これを変更しても問題ありませんが、変更する必要はないと思います。reloadDataドキュメントに従って、すべての可視セルを再ロードする必要があります。

他の誰かがこれを見たことがありますか?


5
ここでも同じですが、iOS7GMでは以前は大丈夫でした。reloadDataviewDidAppearの後に呼び出すと、問題とその恐ろしい回避策が解決されるようで、修正が必要です。誰かがここで助けてくれるといいのですが。
jasonIM 2013

1
同じ問題がある。iOS6で正常に機能するために使用されるコード。今細胞の適切な数を返すにもかかわらず、cellforitematindexpathを呼び出していない
Avnerバール

これは7.0以降のリリースで修正されましたか?
William Jockusch 2014年

私はまだこの問題に関連する問題に直面しています。
Anil

[collectionView setFrame]をオンザフライで変更した後の同様の問題。データソースの数に関係なく、常に1つのセルをデキューします。ここですべてを試してみましたが、それを回避することはできません。
RegularExpression 2014年

回答:


72

メインスレッドでこれを強制します。

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

1
もっと説明できるかわかりません。検索、調査、テスト、プロービング後。これはiOS 7のバグだと思います。メインスレッドを強制すると、UIKit関連のすべてのメッセージが実行されます。別のView Controllerからビューにポップすると、私はこれに遭遇するようです。viewWillAppearのデータを更新します。データとコレクションビューのリロード呼び出しを確認できましたが、UIは更新されませんでした。メインスレッド(UIスレッド)を強制すると、魔法のように動作し始めます。これは、IOS 7である
Shaunti Fondrisi

6
メインスレッドからreloadDataを呼び出せない(メインスレッドからビューを更新できない)ため、それほど意味がありません。これは、いくつかの競合状態のために必要な結果をもたらす副作用である可能性があります。
Raphael Oliveira

7
メインキューからメインキューにディスパッチすると、次の実行ループまで実行が遅れるだけで、現在キューに入れられているすべてのものが最初に実行される可能性があります。
ジョニー2015

2
ありがとう!! コアデータリクエストが時間を消費し、答えが遅れているため、またはwillDisplayCellでデータを再読み込みしているため、ジョニーの引数が正しいかどうかまだわかりません。
フィデル・ロペス

1
うわー、いつもこれはまだ出てくる。これは実際に競合状態であるか、ビューイベントのライフサイクルに関連しています。「ウィル」の表示は既に描かれているはずです。良い洞察ジョニー、ありがとう。このアイテムを最終的に「回答済み」に設定できると思いますか?
Shaunti Fondrisi 16

64

私の場合、データソースのセル/セクションの数は変更されておらず、画面に表示されているコンテンツをリロードしたいだけです。

私は何とかしてこれを回避することができました:

[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];

次に:

[self.collectionView reloadData];

6
その行が原因でアプリがクラッシュしました-"***アサーションエラー-[UICollectionView _endItemAnimations]、/SourceCache/UIKit_Sim/UIKit-2935.137/UICollectionView.m:3840"
Lugubrious

@Lugubrious他のアニメーションを同時に実行している可能性があります。それらをperformBatchUpdates:completion:ブロックに入れてみますか?
liamnichols 2014

これでうまくいきましたが、なぜ必要なのか理解できません。問題は何ですか?
Jon Evans

@JonEvans残念ながら私にはわかりません。それはiOSのバグの一種だと思います。それ以降のバージョンで解決されたかどうかはわかりませんが、それ以降テストしていないため、問題が発生したプロジェクトはありません。より長い私の問題:)
liamnichols 14

1
このバグは単なるでたらめです!コレクションに特定のタイプのセルが1つある場合にのみ、collectionViewをリロードすると、すべてのセルが-ランダムに-消えました。何が起こっているのか理解できず、2日間を失いました。ソリューションを適用して機能するようになりましたが、なぜ機能しているのかはまだわかりません。それはとてもイライラします!とにかく、助けてくれてありがとう:D !!
Cyber​​Dandy 2014

26

私はまったく同じ問題を抱えていましたが、何が問題かを見つけることができました。私の場合、collectionView:cellForItemAtIndexPath:からreloadDataを呼び出していましたが、これは正しくないようです。

reloadDataの呼び出しをメインキューにディスパッチすると、問題が永久に修正されました。

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

1
この行が[self.collectionData.collectionViewLayout invalidateLayout]の意味を教えてください。
iOSDeveloper 2014年

これは私にとってもそれを解決しました-私の場合reloadData、変更オブザーバーによって呼び出されていました。
sudoをインストールします

また、これが適用されますcollectionView(_:willDisplayCell:forItemAtIndexPath:)
Stefan Arambasich 2016

20

一部のアイテムを再読み込みしてもうまくいきませんでした。私の場合、使用しているcollectionViewにセクションが1つしかないため、その特定のセクションをリロードするだけです。今回はコンテンツが正しくリロードされます。奇妙なことに、これはiOS 7(7.0.3)でのみ発生します

[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];

12

iOS 7のreloadDataでも同じ問題が発生しました。長いデバッグセッションの後に問題が見つかりました。

iOS7では、UICollectionViewのreloadDataは、まだ完了していない以前の更新(performBatchUpdates:ブロック内で呼び出された更新)をキャンセルしません。

このバグを解決する最善の解決策は、現在処理されているすべての更新を停止してreloadDataを呼び出すことです。performBatchUpdatesのブロックをキャンセルまたは停止する方法が見つかりませんでした。したがって、バグを解決するために、現在処理されているperformBatchUpdatesブロックがあるかどうかを示すフラグを保存しました。現在処理されている更新ブロックがない場合は、reloadDataをすぐに呼び出すことができ、すべてが期待どおりに機能します。現在処理されている更新ブロックがある場合は、performBatchUpdatesの完全なブロックでreloadDataを呼び出します。


更新したすべての内部でperformBatchUpdateを実行する場所はどこですか?いくつかはアウトですか?全く?非常に興味深い投稿。
VaporwareWolf 2014年

NSFetchedResultsControllerでコレクションビューを使用して、CoreDataからのデータを表示しています。NSFetchedResultsControllerデリゲートが変更を通知すると、すべての更新を収集して、performBatchUpdates内でそれらを呼び出します。NSFetchedResultsControllerリクエスト述語が変更された場合、reloadDataを呼び出す必要があります。
user2459624 14

これは実際には質問への良い答えです。reloadItems()(アニメーション化されている)を実行してからreloadData()を実行すると、セルがスキップされます。
バイオ

12

スイフト5 – 4 – 3

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

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

スウィフト2

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

4

私もこの問題を抱えていました。偶然にも、テストのために再読み込みを強制するためにコレクションビューの上にボタンを追加しました-そして突然、メソッドが呼び出され始めました。

また、単純なものを追加するだけです

UIView *aView = [UIView new];
[collectionView addSubView:aView];

メソッドが呼び出される

また、私はフレームサイズをいじってみました。

iOS7 UICollectionViewには多くのバグがあります。


他の人もこの問題を経験していることを(正規の方法で)見てうれしいです。回避策をありがとう。
VaporwareWolf 2013年

3

この方法を使用できます

[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];

以下のメソッドを使用してすべてのセクションと行のループを繰り返すことによりindexPath、すべてのオブジェクトUICollectionViewを配列に追加できますarrayOfAllIndexPaths

[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];

理解していただければ問題が解決することを願っています。これ以上の説明が必要な場合は返信してください。


3

Shaunti Fondrisiによるソリューションはほぼ完璧です。しかし、のエンキュー実行のようなコードやコードのように一片UICollectionViewreloadData()NSOperationQueuemainQueue確かに作ることができます実行ループの次のイベントループの先頭に実行タイミングを置く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としているときに観察でき、次のイベントループが始まったばかりのときに観察できます。

そこだけ1つのであるのでNSRunLoopあたりのインスタンスNSThreadNSRunLoop正確に駆動し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
        }
    }
}

前のコードでは、我々は今の実行を派遣することができますUICollectionViewreloadData()コードのように一片によって現在のイベントループの最後に:

NSRunLoop.currentRunLoop().perform({ () -> Void in
     collectionView.reloadData()
    }).when(.CurrentLoopEnded)

実際、そのようなNSRunLoopベースのタスクディスパッチャーは、すでに私が使用しているフレームワークの1つであるNestに含まれています。そして、ここにGitHub上のリポジトリがあります:https : //github.com/WeZZard/Nest


2
 dispatch_async(dispatch_get_main_queue(), ^{

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


        });

それは私のために働いた。


1

まずこのスレッドをありがとう、とても参考になりました。他のユーザーが選択できたのに対し、特定のセルを永続的に選択できなくなったという症状があったことを除いて、データの再読み込みで同様の問題がありました。indexPathsForSelectedItemsメソッドまたは同等の関数への呼び出しはありません。デバッグでデータの再読み込みが指摘されました。上記の両方のオプションを試しました。そして、ReloadItemsAtIndexPathsオプションを採用することになりました。私の場合、他のオプションが機能しないか、コレクションビューを1ミリ秒ほど点滅させていたためです。以下のコードはうまくいきます:

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

0

iOS 8.1 sdkでも同じことが起こりましたがdatasource、メソッドを更新した後でも、メソッドnumberOfItemsInSection:が新しいアイテム数を返さないことに気付いたとき、私はそれを正しく理解しました。カウントを更新し、機能させました。


どのようにしてそのカウントを更新してください。.上記の方法はすべて、迅速な3では私にとって
うまくいき

0

UICollectionView.contentInsetを設定しますか?左と右のEdgeInsetを削除します。削除した後は問題ありません。iOS8.3にはまだバグが存在します。


0

UICollectionView Delegateメソッドのそれぞれが期待どおりの動作をすることを確認します。たとえば、

collectionView:layout:sizeForItemAtIndexPath:

有効なサイズが返されない、リロードは機能しません...


0

このコードを試してください。

 NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];

    if (visibleIdx.count) {
        [self.collectionView reloadItemsAtIndexPaths:visibleIdx];
    }

0

これが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
}

-1
inservif (isInsertHead) {
   [self insertItemsAtIndexPaths:tmpPoolIndex];
   NSArray * visibleIdx = [self indexPathsForVisibleItems];
   if (visibleIdx.count) {
       [self reloadItemsAtIndexPaths:visibleIdx];
   }
}else if (isFirstSyncData) {
    [self reloadData];
}else{
   [self insertItemsAtIndexPaths:tmpPoolIndex];
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.