UICollectionView装饰视图

有没有人实现了iOS 6 UICollectionView的装饰视图? 在网上实现装饰视图是不可能的。 基本上在我的应用程序,我有多个部分,我只是想显示每个部分后面的装饰视图。 这应该很容易实现,但我没有运气。 这使我疯狂…谢谢。

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


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

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

为了说明,我将inheritanceUICollectionViewFlowLayout,在集合视图的内容矩形的顶部添加一个标题标签。 这可能是一个装饰视图的愚蠢使用,但它完美地说明了基本原则。 为了简单起见,我将首先硬编码整个事情,让客户端无法定制这个视图的任何方面。

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

  1. 定义一个UICollectionReusableView子类。

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

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

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

最后一步是装饰视图出现在集合视图中的原因。 当集合视图调用layoutAttributesForElements(in:) ,它会发现生成的数组包含指定types装饰视图的布局属性。 集合视图对装饰视图一无所知,所以它回到了布局,要求这种装饰视图的实际实例。 你已经注册了这种装饰视图来对应你的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") } } 

现在我们转到我们称为MyFlowLayout的UICollectionViewLayout子类。 我们在布局的初始化程序中注册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:) ; 这里的索引path是任意的(我在前面的代码中忽略了它):

 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.contains(decatts.frame) { arr.append(decatts) } } return arr } 

这工作! 阅读集合视图顶部的标题标签为“Testing”。


现在我将展示如何使标签可定制。 而不是标题“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实例没有时间相遇。

解决scheme是使用布局属性作为一个信使。 MyFlowLayout永远不符合MyTitleView,但它确实创build了传递给集合视图的布局属性对象来configurationMyFlowLayout。 所以布局属性对象就像一个信封。 通过inheritanceUICollectionViewLayoutAttributes,我们可以在信封中包含任何我们喜欢的信息,比如标题:

 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(_:)方法。 这将在集合视图configuration装饰视图时调用 – 以布局属性对象为参数! 所以我们把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 } } } 

很容易看到你如何扩展这个例子来制作可定制的字体和高度等标签function。 由于我们是UICollectionViewFlowLayout的子类化,因此可能还需要进一步的修改,通过按下其他元素为装饰视图腾出空间。 此外,在技术上,我们应该重写MyElevel isEqual(_:)在MyTitleView来区分不同的标题。 所有这些都是为了读者的一个练习。

我得到这个与自定义布局与以下工作:

创build一个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的控制器中用下面的代码注册这个子类(用你的自定义布局replace代码)

 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

更新:这可能是更好的子类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... } 

最终结果类似于: 在这里输入图像说明

如果你想在collectionview中添加d​​ecorationview(Header / Footer)

请检查此页眉和页脚中的这个链接添加为装饰视图

http://www.appcoda.com/ios-collection-view-tutorial/