有没有人为 iOS 6 UICollectionView 实现过装饰 View ?不可能
查找有关在网络上实现装饰 View 的任何教程。基本上在我的应用程序中,我有多个部分,我只想在每个部分后面显示一个装饰 View 。这应该很容易实现,但我没有运气。这让我发疯...谢谢。
最佳答案
这是一个 集合 View 布局装饰 View 教程在 Swift 中(这是 Swift 3,Xcode 8 种子 6)。
装饰 View 不是 UICollectionView 功能;它们本质上属于 UICollectionViewLayout。没有 UICollectionView 方法(或委托(delegate)或数据源方法)提到装饰 View 。 UICollectionView 对它们一无所知;它只是按照它所说的去做。
要提供任何装饰 View ,您需要一个 UICollectionViewLayout 子类;这个子类可以自由定义自己的属性并委托(delegate)协议(protocol)方法来自定义其装饰 View 的配置方式,但这完全取决于您。
为了说明这一点,我将继承 UICollectionViewFlowLayout 以在集合 View 的内容矩形的顶部添加一个标题标签。这可能是装饰 View 的愚蠢使用,但它完美地说明了基本原理。为简单起见,我将从对整个内容进行硬编码开始,使客户端无法自定义此 View 的任何方面。
在布局子类中实现装饰 View 有四个步骤:
register(_:forDecorationViewOfKind:)
向布局(不是集合 View )注册 UICollectionReusableView 子类.布局的初始值设定项是执行此操作的好地方。 layoutAttributesForDecorationView(ofKind:at:)
返回定位 UICollectionReusableView 的布局属性。要构造布局属性,请调用 init(forDecorationViewOfKind:with:)
并配置属性。 layoutAttributesForElements(in:)
使得 layoutAttributesForDecorationView(ofKind:at:)
的结果包含在返回的数组中。 最后一步是导致装饰 View 出现在集合 View 中的原因。当集合 View 调用
layoutAttributesForElements(in:)
,它发现结果数组包含指定类型的装饰 View 的布局属性。集合 View 对装饰 View 一无所知,因此它返回到布局,请求这种装饰 View 的实际实例。您已经注册了这种装饰 View 以对应于您的 UICollectionReusableView 子类,因此您的 UICollectionReusableView 子类被实例化并返回该实例,并且集合 View 根据布局属性对其进行定位。因此,让我们按照步骤操作。定义 UICollectionReusableView 子类:
class MyTitleView : UICollectionReusableView {
weak var lab : UILabel!
override init(frame: CGRect) {
super.init(frame:frame)
let lab = UILabel(frame:self.bounds)
self.addSubview(lab)
lab.autoresizingMask = [.flexibleWidth, .flexibleHeight]
lab.font = UIFont(name: "GillSans-Bold", size: 40)
lab.text = "Testing"
self.lab = lab
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
现在我们转向我们的 UICollectionViewLayout 子类,我将其称为 MyFlowLayout。我们在布局的初始值设定项中注册 MyTitleView;我还定义了剩余步骤所需的一些私有(private)属性:
private let titleKind = "title"
private let titleHeight : CGFloat = 50
private var titleRect : CGRect {
return CGRect(10,0,200,self.titleHeight)
}
override init() {
super.init()
self.register(MyTitleView.self, forDecorationViewOfKind:self.titleKind)
}
实现
layoutAttributesForDecorationView(ofKind:at:)
:override func layoutAttributesForDecorationView(
ofKind elementKind: String, at indexPath: IndexPath)
-> UICollectionViewLayoutAttributes? {
if elementKind == self.titleKind {
let atts = UICollectionViewLayoutAttributes(
forDecorationViewOfKind:self.titleKind, with:indexPath)
atts.frame = self.titleRect
return atts
}
return nil
}
覆盖
layoutAttributesForElements(in:)
;这里的索引路径是任意的(我在前面的代码中忽略了它):override func layoutAttributesForElements(in rect: CGRect)
-> [UICollectionViewLayoutAttributes]? {
var arr = super.layoutAttributesForElements(in: rect)!
if let decatts = self.layoutAttributesForDecorationView(
ofKind:self.titleKind, at: IndexPath(item: 0, section: 0)) {
if rect.intersects(decatts.frame) {
arr.append(decatts)
}
}
return arr
}
这有效!在集合 View 的顶部会出现一个名为“Testing”的标题标签。
现在我将展示如何使标签可自定义。我们将允许客户端设置确定标题的属性,而不是标题“Testing”。我会给我的布局子类一个公共(public)
title
属性(property):class MyFlowLayout : UICollectionViewFlowLayout {
var title = ""
// ...
}
使用此布局的人应设置此属性。例如,假设这个集合 View 显示了美国的 50 个州:
func setUpFlowLayout(_ flow:UICollectionViewFlowLayout) {
flow.headerReferenceSize = CGSize(50,50)
flow.sectionInset = UIEdgeInsetsMake(0, 10, 10, 10)
(flow as? MyFlowLayout)?.title = "States" // *
}
我们现在遇到一个奇怪的谜题。我们的布局有一个
title
属性,其值需要以某种方式传达给我们的 MyTitleView 实例。但这什么时候可能发生?我们不负责实例化 MyTitleView;当集合 View 在幕后询问实例时,它会自动发生。 MyFlowLayout 实例和 MyTitleView 实例没有相遇的时刻。解决方案是使用布局属性作为信使。 MyFlowLayout 从来没有遇到过 MyTitleView,但它确实创建了传递给集合 View 以配置 MyFlowLayout 的布局属性对象。所以布局属性对象就像一个信封。通过继承 UICollectionViewLayoutAttributes,我们可以在这个信封中包含我们喜欢的任何信息——比如标题:
class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
var title = ""
}
这是我们的信封!现在我们重写
layoutAttributesForDecorationView
的实现.当我们实例化布局属性对象时,我们实例化我们的子类并设置它的title
属性(property):override func layoutAttributesForDecorationView(
ofKind elementKind: String, at indexPath: IndexPath) ->
UICollectionViewLayoutAttributes? {
if elementKind == self.titleKind {
let atts = MyTitleViewLayoutAttributes( // *
forDecorationViewOfKind:self.titleKind, with:indexPath)
atts.title = self.title // *
atts.frame = self.titleRect
return atts
}
return nil
}
最后,在 MyTitleView 中,我们实现了
apply(_:)
方法。这将在集合 View 配置装饰 View 时调用——以布局属性对象作为其参数!所以我们拉出title
并将其用作我们标签的文本:class MyTitleView : UICollectionReusableView {
weak var lab : UILabel!
// ... the rest as before ...
override func apply(_ atts: UICollectionViewLayoutAttributes) {
if let atts = atts as? MyTitleViewLayoutAttributes {
self.lab.text = atts.title
}
}
}
很容易看出您可以如何扩展示例以使诸如字体和高度之类的标签功能可自定义。由于我们是 UICollectionViewFlowLayout 的子类,因此可能还需要一些进一步的修改,通过向下推其他元素来为装饰 View 腾出空间。此外,从技术上讲,我们应该覆盖
isEqual(_:)
在 MyTitleView 中区分不同的标题。所有这些都留给读者作为练习。
关于ios - UICollectionView 装饰 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12810628/