UICollectionView装饰视图

时间:2012-10-10 01:15:59

标签: ios ios6 uicollectionview

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

4 个答案:

答案 0 :(得分:62)

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

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

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

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

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

  1. 定义UICollectionReusableView子类。

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

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

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

  5. 最后一步是导致装饰视图出现在集合视图中的原因。当集合视图调用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")
        }
    }
    

    现在我们转向我们的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)
    }
    

    实施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
    }
    

    这个有效!标题标签为“测试''出现在集合视图的顶部。

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

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

    使用此布局的人应设置此属性。例如,假设此集合视图显示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;当集合视图在后台请求实例时,它会自动发生。 MyFlowLayout实例和MyTitleView实例不会相遇。

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

    class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
        var title = ""
    }
    

    我们的信封!现在我们重写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
    }
    

    最后,在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
            }
        }
    }
    

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

答案 1 :(得分: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

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

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

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

- (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;
}

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

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

可能更好

答案 2 :(得分: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...
}

最终结果类似于: enter image description here

答案 3 :(得分:0)

在我的情况下: 我想从UITableView升级到UICollectionView。

uitableview sections&gt;&gt;&gt;补充意见

uitableview headerView&gt;&gt;&gt;装饰视图

在我的情况下,我觉得子类化布局和做其他的东西,它是“太多” 只是简单的“headerView”(装饰)

所以我的解决方案只是创建标题视图(不是部分)作为第一个单元格 1 部分作为第一部分(第0部分的大小为零)