UICollectionView 装饰视图 [英] UICollectionView Decoration View

查看:13
本文介绍了UICollectionView 装饰视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有人为 iOS 6 UICollectionView 实现了装饰视图?不可能查找有关在网络上实现装饰视图的任何教程.基本上在我的应用程序中,我有多个部分,我只想在每个部分后面显示一个装饰视图.这应该很容易实现,但我没有运气.这让我发疯了...谢谢.

Has anyone implemented a decoration view for the iOS 6 UICollectionView? It's impossible to find any tutorial on implementing a decoration view on the web. Basically in my app I have multiple sections, and I just wanted to display a decoration view behind each section. This should be simple to implement but I'm having no luck. This is driving me nuts... Thanks.

推荐答案

这里有一个集合视图布局装饰视图教程 in Swift(这是Swift 3,Xcode 8 seed 6).

Here's a collection view layout decoration view tutorial in Swift (this is Swift 3, Xcode 8 seed 6).

装饰视图不是 UICollectionView 功能;它们本质上属于 UICollectionViewLayout.没有 UICollectionView 方法(或委托或数据源方法)提及装饰视图.UICollectionView 对它们一无所知;它只是做它被告知的事情.

Decoration views are not a UICollectionView feature; they essentially belong to the UICollectionViewLayout. No UICollectionView methods (or delegate or data source methods) mention decoration views. The UICollectionView knows nothing about them; it simply does what it is told.

要提供任何装饰视图,您需要一个 UICollectionViewLayout 子类;这个子类可以自由定义自己的属性和委托协议方法来自定义其装饰视图的配置方式,但这完全取决于您.

To supply any decoration views, you will need a UICollectionViewLayout subclass; this subclass is free to define its own properties and delegate protocol methods that customize how its decoration views are configured, but that's entirely up to you.

为了说明,我将继承 UICollectionViewFlowLayout 以在集合视图的内容矩形的顶部添加一个标题标签.这可能是对装饰视图的愚蠢使用,但它完美地说明了基本原理.为简单起见,我将从对整个内容进行硬编码开始,让客户端无法自定义此视图的任何方面.

To illustrate, I'll subclass UICollectionViewFlowLayout to impose a title label at the top of the collection view's content rectangle. This is probably a silly use of a decoration view, but it illustrates the basic principles perfectly. For simplicity, I'll start by hard-coding the whole thing, giving the client no ability to customize any aspect of this view.

在布局子类中实现装饰视图有四个步骤:

There are four steps to implementing a decoration view in a layout subclass:

  1. 定义一个 UICollectionReusableView 子类.

  1. Define a UICollectionReusableView subclass.

通过调用 register(_:forDecorationViewOfKind:) 向布局注册 UICollectionReusableView 子类(不是集合视图).布局的初始化器是执行此操作的好地方.

Register the UICollectionReusableView subclass with the layout (not the collection view), by calling register(_:forDecorationViewOfKind:). The layout's initializer is a good place to do this.

实现 layoutAttributesForDecorationView(ofKind:at:) 以返回定位 UICollectionReusableView 的布局属性.要构造布局属性,请调用 init(forDecorationViewOfKind:with:) 并配置属性.

Implement layoutAttributesForDecorationView(ofKind:at:) to return layout attributes that position the UICollectionReusableView. To construct the layout attributes, call init(forDecorationViewOfKind:with:) and configure the attributes.

重写 layoutAttributesForElements(in:) 以便 layoutAttributesForDecorationView(ofKind:at:) 的结果包含在返回的数组中.

Override layoutAttributesForElements(in:) so that the result of layoutAttributesForDecorationView(ofKind:at:) is included in the returned array.

最后一步是导致装饰视图出现在集合视图中的原因.当集合视图调用 layoutAttributesForElements(in:) 时,它发现结果数组包含指定种类的装饰视图的布局属性.集合视图对装饰视图一无所知,所以它回到布局,要求这种装饰视图的实际实例.您已经注册了这种装饰视图以对应您的 UICollectionReusableView 子类,因此您的 UICollectionReusableView 子类被实例化并返回该实例,并且集合视图根据布局属性对其进行定位.

The last step is what causes the decoration view to appear in the collection view. When the collection view calls layoutAttributesForElements(in:), it finds that the resulting array includes layout attributes for a decoration view of a specified kind. The collection view knows nothing about decoration views, so it comes back to the layout, asking for an actual instance of this kind of decoration view. You've registered this kind of decoration view to correspond to your UICollectionReusableView subclass, so your UICollectionReusableView subclass is instantiated and that instance is returned, and the collection view positions it in accordance with the layout attributes.

所以让我们按照步骤操作.定义 UICollectionReusableView 子类:

So let's follow the steps. Define the UICollectionReusableView subclass:

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;我还定义了剩余步骤所需的一些私有属性:

Now we turn to our UICollectionViewLayout subclass, which I'll call MyFlowLayout. We register MyTitleView in the layout's initializer; I've also defined some private properties that I'll need for the remaining steps:

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 layoutAttributesForElements(in:); the index path here is arbitrary (I ignored it in the preceding code):

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
}

这行得通!标题标签Testing"出现在集合视图的顶部.

This works! A title label reading ``Testing'' appears at the top of the collection view.

现在我将展示如何使标签可自定义.我们将允许客户端设置确定标题的属性,而不是标题测试".我会给我的布局子类一个公共的 title 属性:

Now I'll show how to make the label customizable. Instead of the title "Testing," we'll allow the client to set a property that determines the title. I'll give my layout subclass a public title property:

class MyFlowLayout : UICollectionViewFlowLayout {
    var title = ""
    // ...
}

使用此布局的人应设置此属性.例如,假设这个集合视图显示美国的 50 个州:

Whoever uses this layout should set this property. For example, suppose this collection view is displaying the 50 U.S. states:

func setUpFlowLayout(_ flow:UICollectionViewFlowLayout) {
    flow.headerReferenceSize = CGSize(50,50)
    flow.sectionInset = UIEdgeInsetsMake(0, 10, 10, 10)
    (flow as? MyFlowLayout)?.title = "States" // *
}

我们现在遇到了一个奇怪的谜题.我们的布局有一个 title 属性,它的值需要以某种方式传递给我们的 MyTitleView 实例.但什么时候可能发生呢?我们不负责实例化 MyTitleView;当集合视图在后台请求实例时,它会自动发生.MyFlowLayout 实例和 MyTitleView 实例没有相遇的时刻.

We now come to a curious puzzle. Our layout has a title property, the value of which needs to be communicated somehow to our MyTitleView instance. But when can that possibly happen? We are not in charge of instantiating MyTitleView; it happens automatically, when the collection view asks for the instance behind the scenes. There is no moment when the MyFlowLayout instance and the MyTitleView instance meet.

解决方案是使用布局属性作为信使.MyFlowLayout 永远不会遇到 MyTitleView,但它确实创建了布局属性对象,该对象被传递给集合视图以配置 MyFlowLayout.所以布局属性对象就像一个信封.通过继承 UICollectionViewLayoutAttributes,我们可以在信封中包含我们喜欢的任何信息——比如标题:

The solution is to use the layout attributes as a messenger. MyFlowLayout never meets MyTitleView, but it does create the layout attributes object that gets passed to the collection view to configure MyFlowLayout. So the layout attributes object is like an envelope. By subclassing UICollectionViewLayoutAttributes, we can include in that envelope any information we like — such as a title:

class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
    var title = ""
}

这是我们的信封!现在我们重写 layoutAttributesForDecorationView 的实现.当我们实例化布局属性对象时,我们实例化我们的子类并设置它的 title 属性:

There's our envelope! Now we rewrite our implementation of layoutAttributesForDecorationView. When we instantiate the layout attributes object, we instantiate our subclass and set its 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(_:) 方法.这将在集合视图配置装饰视图时调用 - 将布局属性对象作为其参数!所以我们把 title 取出来作为我们标签的文本:

Finally, in MyTitleView, we implement the apply(_:) method. This will be called when the collection view configures the decoration view — with the layout attributes object as its parameter! So we pull out the title and use it as the text of our label:

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 的子类,因此可能还需要进行一些进一步的修改,以便通过下推其他元素来为装饰视图腾出空间.此外,从技术上讲,我们应该在 MyTitleView 中覆盖 isEqual(_:) 以区分不同的标题.所有这些都留给读者作为练习.


It's easy to see how you might extend the example to make such label features as font and height customizable. Since we are subclassing UICollectionViewFlowLayout, some further modifications might also be needed to make room for the decoration view by pushing down the other elements. Also, technically, we should override isEqual(_:) in MyTitleView to differentiate between different titles. All of that is left as an exercise for the reader.

这篇关于UICollectionView 装饰视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆