2

I just can´t find out how to get access to custom layout attributes in the apply() method of a custom cell.

I have to implement a custom layout attribute to my CollectionViewLayoutAtrributes, so I subclassed them. This works well so far:

class TunedLayoutAttributes: UICollectionViewLayoutAttributes {

    var customLayoutAttributeValue: CGFloat = 1000

    override func copy(with zone: NSZone?) -> Any {
        let copy = super.copy(with: zone) as! TunedLayoutAttributes
        customLayoutAttributeValue = customLayoutAttributeValue
        return copy
    }

    override func isEqual(_ object: Any?) -> Bool {
        if let attributes = object as? TunedLayoutAttributes {
            if attributes. customLayoutAttributeValue == customLayoutAttributeValue {
                return super.isEqual (object)
            }
        }
        return false
    }
}

The value has to dynamically change based on user scroll interaction. Now I need my custom cells to update their appearance after an invalidateLayout call from the custom UICollectionViewLayout class. To my knowledge this usually can also be done by overriding the cells classes apply(_ layoutAttributes: UICollectionViewLayoutAttributes) method.

Usually like so:

override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {

    let newFrame = // calculations here. CGRect(....) 
    layoutAttributes.frame = newFrame
}




Now the missing chemistry:

Unlike in the apply() example above my new customLayoutAttributeValue is (of course?) not part of the layoutAttributes: in the method.

So I tried to downcast the layoutAttributes to my custom class like so:

override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {

    let tunedLayoutAttributes = layoutAttributes as! TunedLayoutAttributes
    tunedLayoutAttributes.customLayoutAttributeValue = // calculation here. 
}

So how do I get access to tunedLayoutAttributes in the apply() method? Any help is appreciated. Thanks for reading!

HelloTimo
  • 558
  • 1
  • 5
  • 18

1 Answers1

4

You kind of gave the solution to yourself here. UICollectionViewLayoutAttributes does (of course) not know about customLayoutAttributeValue, so you have to cast to the appropriate class (TunedLayoutAttributes, in your case).

Now in order for apply(_:) to actually give you TunedLayoutAttributes and not just plain UICollectionViewLayoutAttributes, you need to tell your UICollectionViewLayout subclass to use your custom class (TunedLayoutAttributes) when vending layout attributes for items in the layout.

You do that by overriding class var layoutAttributesClass in you layout subclass.

Note that if you override layout attributes vending methods (layoutAttributesForElements(in:) and friends) in your layout subclass, you'd need to return TunedLayoutAttributes there for the whole thing to work.

Also note that UIKit frequently copies attributes under the hood when performing collection view layout passes, so make sure your copy and isEqual methods are working as expected. (e.g., the attributes objects passed to apply(_:) are not (necessarily) the objects your layout vended, but rather copies.

Edit

As discussed in the comments, you should replace the forcecast as! with an if let as? cast to prevent crashes when apply(_:) actually gets passed plain UICollectionViewLayoutAttributes.

CodingMeSwiftly
  • 3,231
  • 21
  • 33
  • Thanks a lot! Somehow I still can't get it to work. In fact you were right – my `override class var layoutAttributesClass: AnyClass { return TunedLayoutAttributes.self }` was commented out :) but something seems to be wrong with this line `let tunedLayoutAttributes = layoutAttributes as! TunedLayoutAttributes`. I receive an error and crash: Could not cast value of type 'UICollectionViewLayoutAttributes' ... to 'timeline_Swift_v002.TunedLayoutAttributes' ... Could not cast value of type 'UICollectionViewLayoutAttributes' ... to '...TunedLayoutAttributes'. – HelloTimo Jul 16 '18 at 13:32
  • Ok this crash is no surprise since you force cast, which can obviously go wrong here. My guess is that the methods you override in your custom layout subclass don't vend actual `TunedLayoutAttributes` but rather `UICollectionViewLayoutAttributes`. Can you confirm that? On a similar note, try adding breakpoints in `copy` and `isEqual` of `TunedLayoutAttributes` and see if control flow even reaches these breakpoints. If not, your layout class is not dealing with your custom attributes class. – CodingMeSwiftly Jul 16 '18 at 13:41
  • 1
    Additionally, try switching out the force cast (`as!`) for an `if let as?` cast and see if you hit the if body. The reason behind this is that maybe your `UICollectionViewCell` subclass (in which you override `apply(_:)` is used in another context (maybe another layout object, maybe another indexPath for which `TunedLayoutAttributes` don't apply) were its actually valid for `UIKit` to pass `UICollectionViewLayoutAttributes` in there. – CodingMeSwiftly Jul 16 '18 at 13:45
  • Amazing! Thanks man. I tried the breakpoints. As far as I understand the idea behind it, they did not confirm your guess. BUT It was the force cast. `if let tunedLayoutAttributes = layoutAttributes as? TunedLayoutAttributes { ... }` was the solution. – HelloTimo Jul 16 '18 at 14:19
  • What your the games rules here? Can I post the solution as another answer in the this thread - how will you get my upvote? – HelloTimo Jul 16 '18 at 14:21
  • I've updated the answer. I guess its ok to accept it now. :) – CodingMeSwiftly Jul 16 '18 at 14:24