Search code examples
iosobjective-cswiftuicollectionviewuicollectionviewlayout

How to subclass UICollectionViewLayoutAttributes


I am trying to subclass UICollectionViewLayoutAttributes so that I can add an extra attribute points (an array of CGPoints).

The subclass is designed to only be used as attributes for decoration views so I need to set the member representedElementCategory to .Decoration. But representedElementCategory is readonly and the only way to set it is through the convenience initialiser:

convenience init(forDecorationViewOfKind decorationViewKind: String, withIndexPath indexPath: NSIndexPath)

Looking at the Objective-C headers I see this convenience initialiser is actually defined as a factory method:

+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath*)indexPath;

I would like my subclass' initialiser to look like:

CustomLayoutAttributes(points: [CGPoint], representedElementKind: String, withIndexPath: NSIndexPath) 

But as subclasses aren't able to call the convenience initialisers of their parents I can't see how this would be possible.

Am I correct in thinking that the only way to subclass this is:

class CustomLayoutAttributes: UICollectionViewLayoutAttributes {
   var points = [CGPoint]()
}

let attribs = UICollectionViewLayoutAttributes(
     forDecorationViewOfKind: "x", 
     withIndexPath: NSIndexPath(forItem: 0, inSection: 0)
) as! CustomLayoutAttributes
attribs.points = [CGPoint(x: 1.0, y: 2.0), CGPoint(x: 4.0, y: 5.0)]

Actually this won't work because the 'as!' cast will fail....


Solution

  • It turns out that a sub class can call a super class' convenience initialiser which can in turn call the sub class' initialiser. For example:

    CustomLayoutAttributes(forDecorationViewOfKind: "decoration1", withIndexPath: indexPath)

    Here: init(forDecorationViewOfKind: String, withIndexPath: NSIndexPath) isn't defined in the CustomLayoutAttributes class but can still be used to construct an instance of the subclass.

    Implementation example:

    // CustomLayoutAttributes.swift
    
    class CustomLayoutAttributes: UICollectionViewLayoutAttributes {
    
       // Additional attribute to test our custom layout
       var points = [CGPoint]()
    
       // MARK: NSCopying
       override func copyWithZone(zone: NSZone) -> AnyObject {
    
          let copy = super.copyWithZone(zone) as! CustomLayoutAttributes
          copy.points = self.points
          return copy
       }
    
        override func isEqual(object: AnyObject?) -> Bool {
    
          if let rhs = object as? CustomLayoutAttributes {
             if points != rhs.points {
                return false
             }
             return super.isEqual(object)
          } else {
             return false
          }
       }
    }
    
    // CustomLayout.swift
    
    override func layoutAttributesForItemAtIndexPath(path: NSIndexPath) -> UICollectionViewLayoutAttributes? {  
    
    
        let attributes = CustomLayoutAttributes(forDecorationViewOfKind: "decoration1", withIndexPath: indexPath)
        attributes.points = [...]
    
        return attributes  
    }