CollectionView is a complex API, while I was developing my wrapper for SwiftUI I had similar bug. I don't have a tendency to jump right away on Apple and prefer investigate and see whether I am not doing something that's expected of me. (I work on AppKit app but this is applicable to UIKit as well)
I inserted the code below into AppDelegate. This allowed me to catch the moment where NSArray.objectAtIndex returns nil, and after little research it turned out that my layout wasn't implementing some callback and wasn't returning attributes for the item at that index, because I didn't understand well enough the purpose of that callback (delegate method). After implementing that method it worked. I don't remember specific details but this is the code you can modify to swizzle NSArray.objectAtIndex, it has to be done in applicationWillFinishLaunching.
// MARK: - swizzling -[NSMutableArray insertObject:atIndex:]
#if false
func swizzle_NSMutableArray() {
let oldClass: AnyClass! = NSClassFromString("__NSArrayM")
guard let oldMethod = class_getInstanceMethod(oldClass, NSMutableArray.oldSelector),
let newMethod = class_getInstanceMethod(oldClass, #selector(NSMutableArray.new_insert(_:at:))) else {
print("unable to swizzle -[NSMutableArray insertObject:atIndex:] method")
return
}
NSMutableArray.oldIMP = method_setImplementation(oldMethod, method_getImplementation(newMethod))
}
extension NSMutableArray {
fileprivate static let oldSelector = NSSelectorFromString("insertObject:atIndex:")
fileprivate static var oldIMP: IMP!
fileprivate static var oldInsertObjectAtIndex: ((NSMutableArray, Any, Int) -> Void)?
@objc
dynamic nonisolated func new_insert(_ object: Any?, at index: Int) {
let old = unsafeBitCast(NSMutableArray.oldIMP, to: (@convention(c) (Any?, Selector?, Any?, Int) -> Void).self)
if object == nil {
print(index)
print(self)
}
return old(self, NSMutableArray.oldSelector, object, index)
}
}
#endif