UICollectionView装饰视图

2020/12/11 11:12 · ios ·  · 0评论

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

这是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:)包含在返回的数组中。

最后一步是什么导致装饰视图出现在集合视图中。当collection视图调用时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
}

这可行!标题视图中显示``正在测试''的标题标签出现在集合视图的顶部。


现在,我将展示如何使标签可定制。我们将允许客户端设置一个确定标题的属性,而不是标题“ Testing”。我将给我的布局子类一个公共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(_:)方法。当集合视图配置装饰视图时将调用此方法-以layout属性对象作为其参数!因此,我们将拔出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的子类,因此可能还需要进行一些进一步的修改,以通过按下其他元素为装饰视图腾出空间。同样,从技术上讲,我们应该isEqual(_:)在MyTitleView中重写以区分不同的标题。所有这些都留给读者作为练习。

我通过以下自定义布局进行工作:

创建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;
}

似乎没有相关文档,但以下文档使我步入正轨:适用于iOS的Collection View编程指南

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

在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...
}

最终结果类似于:
在此处输入图片说明

就我而言:我想从UITableView升级到UICollectionView。

uitableview部分>>>补充视图

uitableview headerView >>>装饰视图

就我而言,我觉得可以继承布局并做其他事情,对于简单的“ headerView”(装饰)来说,这“太多了”

所以我的解决方案是将headerview(不是section)创建为第一个单元格,
并将section
1创建为第一部分(section 0的大小为零)

本文地址:http://ios.askforanswer.com/uicollectionviewzhuangshishitu.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 admin 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!