Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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....

like image 649
toriaezunama Avatar asked Jul 22 '15 22:07

toriaezunama


2 Answers

UICollectionViewLayoutAttributes subclassing require some peculiars. Have you check subclassing not at https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewLayoutAttributes_class/

In most cases, you use this class as-is. If you want to supplement the base layout attributes with custom layout attributes, you can subclass and define whatever properties you want to store the additional layout data. Because layout attribute objects may be copied by the collection view, make sure your subclass conforms to the NSCopying protocol by implementing any methods appropriate for copying your custom attributes to new instances of your subclass. In addition to defining your subclass, your UICollectionReusableView objects need to implement the applyLayoutAttributes: method so that they can apply any custom attributes at layout time.

If you subclass and implement any custom layout attributes, you must also override the inherited isEqual: method to compare the values of your properties. In iOS 7 and later, the collection view does not apply layout attributes if those attributes have not changed. It determines whether the attributes have changed by comparing the old and new attribute objects using the isEqual: method. Because the default implementation of this method checks only the existing properties of this class, you must implement your own version of the method to compare any additional properties. If your custom properties are all equal, call super and return the resulting value at the end of your implementation.

like image 150
Hugues BR Avatar answered Oct 31 '22 18:10

Hugues BR


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  
}
like image 44
toriaezunama Avatar answered Oct 31 '22 19:10

toriaezunama