UICollectionView装饰视图

vtr*_*ong 39 ios ios6 uicollectionview

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

mat*_*att 71

这是Swift中的集合视图布局装饰视图教程(这是Swift 3,Xcode 8种子6).


装饰视图不是UICollectionView功能; 它们基本上属于UICollectionViewLayout.没有UICollectionView方法(或委托或数据源方法)提到装饰视图.UICollectionView对它们一无所知; 它只是按照它所说的去做.

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

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

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

  1. 定义UICollectionReusableView子类.

  2. 通过调用将布局(而不是集合视图)注册到UICollectionReusableView子类register(_:forDecorationViewOfKind:).布局的初始化程序是一个很好的地方.

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

  4. 覆盖layoutAttributesForElements(in:)以使结果layoutAttributesForDecorationView(ofKind:at:)包含在返回的数组中.

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

所以让我们按照步骤进行操作.定义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")
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们转向我们的UICollectionViewLayout子类,我将其称为MyFlowLayout.我们在布局的初始化程序中注册了MyTitleView; 我还定义了一些我需要用于其余步骤的私有属性:

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)
}
Run Code Online (Sandbox Code Playgroud)

实施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
}
Run Code Online (Sandbox Code Playgroud)

覆盖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
}
Run Code Online (Sandbox Code Playgroud)

这有效!读取"测试"的标题标签出现在集合视图的顶部.


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

class MyFlowLayout : UICollectionViewFlowLayout {
    var title = ""
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

解决方案是使用布局属性作为信使.MyFlowLayout从不满足MyTitleView,但它确实创建了布局属性对象,该对象被传递到集合视图以配置MyFlowLayout.所以布局属性对象就像一个信封.通过子类化UICollectionViewLayoutAttributes,我们可以在该信封中包含我们喜欢的任何信息 - 例如标题:

class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
    var title = ""
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

最后,在MyTitleView中,我们实现了该apply(_:)方法.当集合视图配置装饰视图时,将调用此方法 - 将布局属性对象作为其参数!因此,我们将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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以很容易地看到如何扩展示例以使字体和高度等标签功能可自定义.由于我们是UICollectionViewFlowLayout的子类,因此可能还需要进一步修改以通过按下其他元素为装饰视图腾出空间.另外,从技术上讲,我们应该isEqual(_:)在MyTitleView中覆盖以区分不同的标题.所有这些都留给读者练习.

  • @matt 非常感谢您关于使用布局属性作为信使与装饰视图进行通信的部分!这是一个有趣的 API - 您不直接实例化或引用装饰视图。 (2认同)

dom*_*ikk 21

我使用以下自定义布局工作:

创建UICollectionReusableView的子类,例如向其添加UIImageView:

@implementation AULYFloorPlanDecorationViewCell

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        UIImage *backgroundImage = [UIImage imageNamed:@"Layout.png"];
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
        imageView.image = backgroundImage;
        [self addSubview:imageView];
    }
    return self;
}

@end
Run Code Online (Sandbox Code Playgroud)

然后在viewDidLoad中的控制器中使用以下代码注册此子类(用自定义布局替换代码)

AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
[automationLayout registerClass:[AULYFloorPlanDecorationViewCell class]  forDecorationViewOfKind:@"FloorPlan"];
Run Code Online (Sandbox Code Playgroud)

在自定义布局中,然后实现以下方法(或类似):

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];
    layoutAttributes.frame = CGRectMake(0.0, 0.0, self.collectionViewContentSize.width, self.collectionViewContentSize.height);
    layoutAttributes.zIndex = -1;
    return layoutAttributes;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *allAttributes = [[NSMutableArray alloc] initWithCapacity:4];

    [allAttributes addObject:[self layoutAttributesForDecorationViewOfKind:@"FloorPlan" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]];

    for (NSInteger i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++)
    {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
        [allAttributes addObject:layoutAttributes];
    }
    return allAttributes;
}
Run Code Online (Sandbox Code Playgroud)

似乎没有相关文档,但以下文档让我走上正轨:适用于iOS的Collection View Programming Guide

更新:将UICollectionReusableView子类化为装饰视图而不是UICollectionViewCell可能更好


Lar*_*ien 5

以下是如何在MonoTouch中执行此操作:

public class DecorationView : UICollectionReusableView
{
    private static NSString classId = new NSString ("DecorationView");
    public static NSString ClassId { get { return classId; } }

    UIImageView blueMarble;

    [Export("initWithFrame:")]
    public DecorationView (RectangleF frame) : base(frame)
    {
        blueMarble = new UIImageView (UIImage.FromBundle ("bluemarble.png"));

        AddSubview (blueMarble);    
    }
}


public class SimpleCollectionViewController : UICollectionViewController
{
    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        //Register the cell class (code for AnimalCell snipped)
        CollectionView.RegisterClassForCell (typeof(AnimalCell), AnimalCell.ClassId);
        //Register the supplementary view class (code for SideSupplement snipped)
        CollectionView.RegisterClassForSupplementaryView (typeof(SideSupplement), UICollectionElementKindSection.Header, SideSupplement.ClassId);

        //Register the decoration view
        CollectionView.CollectionViewLayout.RegisterClassForDecorationView (typeof(DecorationView), DecorationView.ClassId);
    }

 //...snip...
}

public class LineLayout : UICollectionViewFlowLayout
{
    public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (RectangleF rect)
    {
        var array = base.LayoutAttributesForElementsInRect (rect);
        /* 
        ...snip content relating to cell layout...
        */
        //Add decoration view
        var attributesWithDecoration = new List<UICollectionViewLayoutAttributes> (array.Length + 1);
        attributesWithDecoration.AddRange (array);
        var decorationIndexPath = NSIndexPath.FromIndex (0);
        var decorationAttributes = LayoutAttributesForDecorationView (DecorationView.ClassId, decorationIndexPath);
        attributesWithDecoration.Add (decorationAttributes);

        var extended = attributesWithDecoration.ToArray<UICollectionViewLayoutAttributes> ();

        return extended;
    }

    public override UICollectionViewLayoutAttributes LayoutAttributesForDecorationView (NSString kind, NSIndexPath indexPath)
    {
        var layoutAttributes = UICollectionViewLayoutAttributes.CreateForDecorationView (kind, indexPath);
        layoutAttributes.Frame = new RectangleF (0, 0, CollectionView.ContentSize.Width, CollectionView.ContentSize.Height);
        layoutAttributes.ZIndex = -1;
        return layoutAttributes;
    }
//...snip...
}
Run Code Online (Sandbox Code Playgroud)

最终结果类似于: 在此输入图像描述