如何子类化UICollectionViewLayoutAttributes

12

我正在尝试对UICollectionViewLayoutAttributes进行子类化,以便我可以添加一个额外的属性points(一个CGPoints数组)。

这个子类仅设计用于作为装饰视图的属性,因此我需要将成员representedElementCategory设置为.Decoration。但是representedElementCategory是只读的,唯一的设置方法是通过方便的初始化器:

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

查看 Objective-C 的头文件,我发现这个方便的初始化器实际上被定义为一个工厂方法:

+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath*)indexPath;
我希望我的子类初始化器看起来像这样:

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

但由于子类无法调用其父级的便利初始化器,我不知道如何实现这个。

我是否正确地理解了唯一的子类化方法是:

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)]

实际上这不会起作用,因为 'as!' 强制类型转换将失败...


我应该补充说明,我正在使用这些布局属性在自定义的UICollectionViewLayout中,因此需要能够创建它们,这就是问题所在。 - toriaezunama
3个回答

14

原来,子类可以调用其超类的便利初始化程序,进而调用子类的初始化程序。例如:

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

这里:init(forDecorationViewOfKind: String, withIndexPath: NSIndexPath)没有在CustomLayoutAttributes类中定义,但仍可用于构造子类的实例。

实现示例:

// 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  
}

2
如果你也在子类化UICollectionViewLayout,别忘了在你的子类中重写layoutAttributesClass方法,返回你自定义属性的Class - Tjalsma

7

UICollectionViewLayoutAttributes的子类化需要一些特殊处理。

你是否查看了https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewLayoutAttributes_class/中关于子类化的说明?

在大多数情况下,您可以直接使用此类。如果您想要通过自定义布局属性来补充基本布局属性,则可以创建子类并定义任何属性以存储其他布局数据。因为集合视图可能会复制布局属性对象,所以请确保您的子类遵循NSCopying协议,并实现适用于将自定义属性复制到新的子类实例的任何方法。除了定义子类之外,您的UICollectionReusableView对象还需要实现applyLayoutAttributes:方法,以便它们可以在布局时应用任何自定义属性。

如果您创建子类并实现任何自定义布局属性,则还必须重写继承的isEqual:方法以比较属性的值。在iOS 7及更高版本中,集合视图不会应用未更改的布局属性。它通过使用isEqual:方法比较旧属性对象和新属性对象来确定属性是否已更改。由于此方法的默认实现仅检查此类的现有属性,因此您必须实现自己版本的该方法以比较任何其他属性。如果您的自定义属性全部相等,则在实现的末尾调用super并返回结果值。


是的,我明白了,谢谢。但是我不明白那如何回答我的问题。 - toriaezunama
1
好的,这可能与layoutAttributesClass没有在UICollectionViewLayout类上定义,而是在其扩展上定义有关。这可能与此问题有关。 - Hugues BR

0

方法名称已更改,因此转换为 Swift 5

    class SceneSelectorAttributes: UICollectionViewLayoutAttributes {

    var value = CGFloat()

    override func copy(with zone: NSZone? = nil) -> Any {
        let copy = super.copy(with: zone) as! SceneSelectorAttributes
        copy.value = self.value
        return copy
    }

    override func isEqual(_ object: Any?) -> Bool {
        if let rhs = object as? SceneSelectorAttributes {
            if value != rhs.value {
                return false
            }
            return super.isEqual(object)
        } else {
            return false
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接