这是一个关于在Swift中使用集合视图布局装饰视图的教程(这是Swift 3,Xcode 8 seed 6)。
装饰视图并不属于UICollectionView的功能;它们实际上属于UICollectionViewLayout。没有任何UICollectionView方法(或委托或数据源方法)提到装饰视图。UICollectionView对它们一无所知;它只是按照指示执行操作。
要提供任何装饰视图,您需要一个UICollectionViewLayout子类;该子类可以自由定义其自己的属性和委托协议方法,以定制其如何配置装饰视图,但完全取决于您。
为了说明问题,我将子类化UICollectionViewFlowLayout,以在集合视图的内容矩形的顶部施加一个标题标签。这可能是一个愚蠢的装饰视图用法,但它完美地阐述了基本原理。为简单起见,我将首先硬编码整个内容,不允许客户端自定义此视图的任何方面。
在布局子类中实现装饰视图有四个步骤:
定义UICollectionReusableView子类。
通过调用register(_:forDecorationViewOfKind:)
,将UICollectionReusableView子类注册到布局中(而不是集合视图)。初始化器是这样做的好地方。
实现layoutAttributesForDecorationView(ofKind:at:)
方法,以返回定位UICollectionReusableView所需的布局属性。为构造布局属性,请调用init(forDecorationViewOfKind:with:)
并配置属性。
重写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")
}
}
现在我们转向我们的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”的标题标签将出现在集合视图的顶部。
现在我将展示如何使标签可定制化。不使用标题“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(_:)
方法。当集合视图配置装饰视图时,将调用此方法,并使用布局属性对象作为其参数!因此,我们提取出 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(_:)方法,以区分不同的标题。所有这些都留给读者作为练习。我使用自定义布局解决了这个问题,具体方法如下:
创建一个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方法中,使用以下代码(将code替换为您自定义的布局)注册此子类:
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类。
[allAttributes addObject:[self layoutAttributesForDecorationViewOfKind:@"FloorPlan" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]];
(您可以为第2、3、4等部分添加更多内容。然后,您必须确保设置布局属性,以便该部分的边界正确。要找出有多少个部分以及它们包含多少个元素,您应该能够访问self.collectionView.dataSource
) - dominikkUICollectionViewLayout
才能使用装饰视图?这可以从FlowLayout中完成吗? - Drew C以下是具体步骤:
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...
}
最终结果类似于:
注:此处为IT技术相关内容,涉及图片展示。