36

This is the code causing the warning:

private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
    let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
    let distance = CGRectGetMidX(attributes!.frame) - self.midX;
    var transform = CATransform3DIdentity;
    transform = CATransform3DTranslate(transform, -distance, 0, -self.width);
    attributes!.transform3D = CATransform3DIdentity;
    return attributes
}

The console also prints:

This is likely occurring because the flow layout "xyz" is modifying attributes returned by UICollectionViewFlowLayout without copying them.

How do I fix this warning?

JAL
  • 41,701
  • 23
  • 172
  • 300
Dhruv Goel
  • 2,715
  • 1
  • 17
  • 17

7 Answers7

52

This is likely occurring because the flow layout "xyz" is modifying attributes returned by UICollectionViewFlowLayout without copying them

And sure enough, that's just what you are doing:

private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
    let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
    let distance = CGRectGetMidX(attributes!.frame) - self.midX;
    var transform = CATransform3DIdentity;
    transform = CATransform3DTranslate(transform, -distance, 0, -self.width);
    attributes!.transform3D = CATransform3DIdentity;
    return attributes
}

I expect that if you simply say:

let attributes = 
    super.layoutAttributesForItemAtIndexPath(indexPath).copy() 
    as! UICollectionViewLayoutAttributes

or similar, the problem will go away.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    In Swift 2, you can't call copy() on super.layoutAttributesForItemAtIndexPath. However you can call it on the individual layout attributes, then put those into an array and return that instead. See http://stackoverflow.com/a/31508225/1259702 – Trevor Alyn Feb 05 '16 at 15:04
  • @TrevorAlyn yes you can. Here is an example of me doing it. https://github.com/mattneub/Programming-iOS-Book-Examples/blob/af4c7daca57b5a995d6c8b59b3362a45712cefdb/bk2ch08p466collectionViewFlowLayout2/ch21p748collectionViewFlowLayout2/MyFlowLayout.swift and the code I give in my answer is Swift 2 and works fine. – matt Feb 05 '16 at 19:16
  • @matt: Oops -- I was trying to call copy() on layoutAttributesForElementsInRect, not layoutAttributesForItemAtIndexPath. But Xcode gives me a "has no member copy" error when I try to do this: var attributes = super.layoutAttributesForElementsInRect(rect).copy() – Trevor Alyn Feb 17 '16 at 22:05
  • @TrevorAlyn If you don't copy it, is it giving you the same error? – matt Feb 18 '16 at 01:46
  • Nope. Fortunately I can copy the individual attributes, like so: attributesCopy.append(attributes![0].copy() as! UICollectionViewLayoutAttributes) – Trevor Alyn Feb 18 '16 at 15:20
  • @TrevorAlyn Right but if you're not having a problem I don't see why you need to make a copy. – matt Feb 18 '16 at 15:27
  • @matt: I'm copying them so I can make adjustments to them. – Trevor Alyn Feb 19 '16 at 14:18
  • **In Swift4.1** I used `override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var array: [UICollectionViewLayoutAttributes] = [] for object in super.layoutAttributesForElements(in: rect)! { array.append(object.copy() as! UICollectionViewLayoutAttributes) }` – Jack May 07 '18 at 12:21
43

In addition to the great answer above.

I know the example code is written in swift, but I thought that it can be helpful to have the Objective-C version.

For Objective-C, this won't work, because the copy function does only a shallow copy. You will have to do this:

NSArray * original   = [super layoutAttributesForElementsInRect:rect];
NSArray * attributes = [[NSArray alloc] initWithArray:original copyItems:YES];

I have added a temp variable for readability.

spenibus
  • 4,339
  • 11
  • 26
  • 35
Georgi Boyadzhiev
  • 1,351
  • 1
  • 13
  • 18
  • In addition : my answer is handy when You use an array of attributes, if You want to get only the attributes for a specific indexPath see the answer of Ayman Ibrahim – Georgi Boyadzhiev Oct 22 '15 at 12:20
23

I had this issue when overriding layoutAttributesForElementsInRect. Iterating through each element in the super.layoutAttributesForElementsInRect(rect) array and calling copy wasn't working for me, so I ended up falling back on Foundation classes and using NSArray's copyItems:

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    // unwrap super's attributes
    guard let superArray = super.layoutAttributesForElementsInRect(rect) else { return nil }

    // copy items
    guard let attributes = NSArray(array: superArray, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }

    // modify attributes

    return attributes
}
JAL
  • 41,701
  • 23
  • 172
  • 300
  • 2
    Thank you for posting this even though there was another answer. I was iterating through each element in an array and copying as well. Wasn't working for me either. So thanks for posting this solution. – Nate4436271 Apr 08 '16 at 15:28
  • @JAL, how to append attributes to it. because its a NSArray – Ranjit Jul 01 '16 at 12:23
  • @Ranjit cast the `NSArray` as a Swift Array like I do with the `attributes` variable – JAL Jul 01 '16 at 15:22
  • @JAL, I solved the copy issue, but I am facing one more issue, I have created a SO question here http://stackoverflow.com/questions/38146913/uicollectionview-custom-flow-layout-crashes-on-scrolling – Ranjit Jul 01 '16 at 15:29
10

It's not the answer to original question, but can help for layoutAttributesForElements(in rect: CGRect) (Swift 3.0):

let safeAttributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }
safeAttributes?.forEach { /* do something with attributes*/ }
Jovan Stankovic
  • 4,661
  • 4
  • 27
  • 16
6

Adding to @Georgi answer

<NSCopying> must be conformed and add copy message call to layoutAttributesForItemAtIndexPath

UICollectionViewLayoutAttributes* attributes = [[super layoutAttributesForItemAtIndexPath:indexPath] copy];
Ayman Ibrahim
  • 1,359
  • 15
  • 24
3

Updated response for Swift 3!

for func layoutAttributesForElements

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

    guard let attributes = super.layoutAttributesForElements(in: rect) else {
        return nil
    }

    guard let attributesToReturn =  attributes.map( { $0.copy() }) as? [UICollectionViewLayoutAttributes] else {
        return nil
    }

    return attributesToReturn

}

for func layoutAttributesForItem

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

    guard let currentItemAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
        return nil
    }

    return currentItemAttributes
}

If you override both function, you have to call copy on both function!

Good coding!

0

I have subclassed UICollectionViewFlowLayout. Inside layoutAttributesForElementsInRect() I did this change:

change from

guard let attributesForItem: UICollectionViewLayoutAttributes = self.layoutAttributesForItemAtIndexPath(indexPath) else {
    return
}

change to

guard let attributesForItem = self.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
    return
}
neoneye
  • 50,398
  • 25
  • 166
  • 151