IOS 7で実行するようにアプリを更新してきましたが、ほとんどの場合はスムーズに進んでいます。複数のアプリで、reloadData
のUICollectionViewController
メソッドが以前のように機能していないことに気付きました。
UICollectionViewController
をロードし、UICollectionView
に通常のデータを入力します。これは初めてうまくいきます。ただし、新しいデータを要求し(UICollectionViewDataSource
を設定)、reloadData
を呼び出すと、numberOfItemsInSection
およびnumberOfSectionsInCollectionView
のデータソースを照会しますが、 cellForItemAtIndexPath
を適切な回数呼び出します。
1つのセクションのみをリロードするようにコードを変更すると、適切に機能します。これらを変更しても問題ありませんが、変更する必要はないと思います。 reloadData
は、ドキュメントに従って、表示されているすべてのセルを再ロードする必要があります。
他の誰かがこれを見ましたか?
メインスレッドでこれを強制します。
dispatch_async(dispatch_get_main_queue(), ^ {
[self.collectionView reloadData];
});
私の場合、データソースのセル/セクションの数は決して変わらず、画面に表示されているコンテンツをリロードしたかっただけです。
私は以下を呼び出すことでこれを回避することができました:
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
その後:
[self.collectionView reloadData];
私はまったく同じ問題を抱えていましたが、何が間違っているのかを見つけることができました。私の場合、私はreloadDataからcollectionView:cellForItemAtIndexPath:を呼び出していましたが、これは正しくないと思われます。
reloadDataの呼び出しをメインキューにディスパッチすると、この問題は永久に修正されました。
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
いくつかのアイテムをリロードしてもうまくいきませんでした。私の場合、使用しているcollectionViewにはセクションが1つしかないため、その特定のセクションをリロードするだけです。今回は、コンテンツが正しくリロードされます。奇妙なことに、これは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)
IOS 7のreloadDataでも同じ問題がありました。長いデバッグセッションの後、問題が見つかりました。
IOS7では、UICollectionViewのreloadDataは、まだ完了していない以前の更新(performBatchUpdates:ブロック内で呼び出した更新)をキャンセルしません。
このバグを解決する最良の解決策は、現在処理されているすべての更新を停止して、reloadDataを呼び出すことです。 performBatchUpdatesのブロックをキャンセルまたは停止する方法が見つかりませんでした。したがって、バグを解決するために、現在処理されているperformBatchUpdatesブロックがあるかどうかを示すフラグを保存しました。現在処理されている更新ブロックがない場合、すぐにreloadDataを呼び出すことができ、すべてが期待どおりに機能します。現在処理されている更新ブロックがある場合、performBatchUpdatesの完全なブロックでreloadDataを呼び出します。
私もこの問題を抱えていました。偶然にも、テストのためにリロードを強制するために、collectionviewの上にボタンを追加しました-そして突然メソッドが呼び出され始めました。
また、単純なものを追加するだけです
UIView *aView = [UIView new];
[collectionView addSubView:aView];
メソッドが呼び出されます
また、フレームサイズをいろいろ試してみました。
IOS7 UICollectionViewには多くのバグがあります。
Shaunti Fondrisiが提供するソリューションはほぼ完璧です。ただし、UICollectionView
のreloadData()
からNSOperationQueue
のmainQueue
への実行をキューに登録するようなコードは、実行の次のイベントループの開始に実行タイミングを実際に配置します。ループ。フリックで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
は次のイベントループが始まったときに観察できます。
NSRunLoop
およびNSThread
ごとにNSRunLoop
インスタンスが1つしかないため、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
}
}
}
以前のコードでは、次のようなコードにより、UICollectionView
のreloadData()
の実行を現在のイベントループの最後にディスパッチできます。
NSRunLoop.currentRunLoop().perform({ () -> Void in
collectionView.reloadData()
}).when(.CurrentLoopEnded)
実際、このようなNSRunLoopベースのタスクディスパッチャは、私の個人的に使用されるフレームワークの1つであるNestに既に含まれています。 GitHubのリポジトリは次のとおりです。 https://github.com/WeZZard/Nest
この方法を使用できます
[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];
indexPath
のすべてのUICollectionView
オブジェクトを配列arrayOfAllIndexPaths
に追加するには、以下のメソッドを使用してすべてのセクションと行のループを繰り返します。
[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];
ご理解いただき、問題を解決できることを願っています。さらに説明が必要な場合は返信してください。
まず、このスレッドをありがとう、とても助かります。特定のセルを他のセルでは永続的に選択できなくなったという症状を除いて、データの再読み込みでも同様の問題が発生しました。 indexPathsForSelectedItemsメソッドまたは同等のメソッドの呼び出しはありません。デバッグがデータのリロードを指摘しました。上記の両方のオプションを試しました。私の場合、他のオプションが機能しないか、コレクションビューを1ミリ秒程度フラッシュするため、ReloadItemsAtIndexPathsオプションを採用しました。以下のコードは正常に機能します。
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];`
このコードを試してください。
NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];
if (visibleIdx.count) {
[self.collectionView reloadItemsAtIndexPaths:visibleIdx];
}
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView reloadData];
[collectionView layoutIfNeeded];
[collectionView reloadData];
});
それは私のために働いた。
UICollectionView.contentInsetを設定していますか?左右のEdgeInsetを削除します。削除しても問題ありません。バグはiOS8.3にも残っています。
UICollectionView Delegateメソッドのそれぞれが期待どおりに動作することを確認します。たとえば、
collectionView:layout:sizeForItemAtIndexPath:
有効なサイズが返されず、リロードが機能しません...
これが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
}
IOS 8.1 sdkでも同じことが起こりましたが、datasource
を更新した後でもメソッドnumberOfItemsInSection:
が新しいアイテム数を返さないことに気付いたとき、私はそれを正しく理解しました。カウントを更新し、機能させました。