ios UICollectionView 装饰视图
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12810628/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UICollectionView Decoration View
提问by vtruong
Has anyone implemented a decoration view for the iOS 6 UICollectionView? It's impossible to find any tutorial on implementing a decoration view on the web. Basically in my app I have multiple sections, and I just wanted to display a decoration view behind each section. This should be simple to implement but I'm having no luck. This is driving me nuts... Thanks.
有没有人为 iOS 6 UICollectionView 实现过装饰视图?不可能在网上找到任何关于实现装饰视图的教程。基本上在我的应用程序中,我有多个部分,我只想在每个部分后面显示一个装饰视图。这应该很容易实现,但我没有运气。这让我发疯...谢谢。
回答by matt
Here's a collection view layout decoration view tutorialin Swift (this is Swift 3, Xcode 8 seed 6).
这是 Swift 中的集合视图布局装饰视图教程(这是 Swift 3,Xcode 8 种子 6)。
Decoration views are not a UICollectionView feature; they essentially belong to the UICollectionViewLayout. No UICollectionView methods (or delegate or data source methods) mention decoration views. The UICollectionView knows nothing about them; it simply does what it is told.
装饰视图不是 UICollectionView 功能;它们本质上属于 UICollectionViewLayout。没有 UICollectionView 方法(或委托或数据源方法)提到装饰视图。UICollectionView 对它们一无所知;它只是按照它所说的去做。
To supply any decoration views, you will need a UICollectionViewLayout subclass; this subclass is free to define its own properties and delegate protocol methods that customize how its decoration views are configured, but that's entirely up to you.
要提供任何装饰视图,您需要一个 UICollectionViewLayout 子类;这个子类可以自由定义自己的属性并委托协议方法来自定义其装饰视图的配置方式,但这完全取决于您。
To illustrate, I'll subclass UICollectionViewFlowLayout to impose a title label at the top of the collection view's content rectangle. This is probably a silly use of a decoration view, but it illustrates the basic principles perfectly. For simplicity, I'll start by hard-coding the whole thing, giving the client no ability to customize any aspect of this view.
为了说明这一点,我将继承 UICollectionViewFlowLayout 以在集合视图的内容矩形的顶部添加一个标题标签。这可能是装饰视图的愚蠢使用,但它完美地说明了基本原理。为简单起见,我将从对整个内容进行硬编码开始,使客户端无法自定义此视图的任何方面。
There are four steps to implementing a decoration view in a layout subclass:
在布局子类中实现装饰视图有四个步骤:
Define a UICollectionReusableView subclass.
Register the UICollectionReusableView subclass with the layout (notthe collection view), by calling
register(_:forDecorationViewOfKind:)
. The layout's initializer is a good place to do this.Implement
layoutAttributesForDecorationView(ofKind:at:)
to return layout attributes that position the UICollectionReusableView. To construct the layout attributes, callinit(forDecorationViewOfKind:with:)
and configure the attributes.Override
layoutAttributesForElements(in:)
so that the result oflayoutAttributesForDecorationView(ofKind:at:)
is included in the returned array.
定义一个 UICollectionReusableView 子类。
注册的布局(在UICollectionReusableView子不集合视图),通过调用
register(_:forDecorationViewOfKind:)
。布局的初始值设定项是执行此操作的好地方。实现
layoutAttributesForDecorationView(ofKind:at:)
以返回定位 UICollectionReusableView 的布局属性。要构造布局属性,请调用init(forDecorationViewOfKind:with:)
并配置属性。覆盖
layoutAttributesForElements(in:)
以便将 的结果layoutAttributesForDecorationView(ofKind:at:)
包含在返回的数组中。
The last step is what causes the decoration view to appear in the collection view. When the collection view calls layoutAttributesForElements(in:)
, it finds that the resulting array includes layout attributes for a decoration view of a specified kind. The collection view knows nothing about decoration views, so it comes back to the layout, asking for an actual instance of this kind of decoration view. You've registered this kind of decoration view to correspond to your UICollectionReusableView subclass, so your UICollectionReusableView subclass is instantiated and that instance is returned, and the collection view positions it in accordance with the layout attributes.
最后一步是导致装饰视图出现在集合视图中的原因。当集合视图调用 时layoutAttributesForElements(in:)
,它发现结果数组包含指定类型的装饰视图的布局属性。集合视图对装饰视图一无所知,因此它返回到布局,请求这种装饰视图的实际实例。您已经注册了这种装饰视图以对应于您的 UICollectionReusableView 子类,因此您的 UICollectionReusableView 子类被实例化并返回该实例,并且集合视图根据布局属性对其进行定位。
So let's follow the steps. Define the UICollectionReusableView subclass:
因此,让我们按照步骤操作。定义 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")
}
}
Now we turn to our UICollectionViewLayout subclass, which I'll call MyFlowLayout. We register MyTitleView in the layout's initializer; I've also defined some private properties that I'll need for the remaining steps:
现在我们转向我们的 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)
}
Implement layoutAttributesForDecorationView(ofKind:at:)
:
实施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
}
Override layoutAttributesForElements(in:)
; the index path here is arbitrary (I ignored it in the preceding code):
覆盖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
}
This works! A title label reading ``Testing'' appears at the top of the collection view.
这有效!在集合视图的顶部会出现一个名为“Testing”的标题标签。
Now I'll show how to make the label customizable. Instead of the title "Testing," we'll allow the client to set a property that determines the title. I'll give my layout subclass a public title
property:
现在我将展示如何使标签可自定义。我们将允许客户端设置确定标题的属性,而不是标题“Testing”。我会给我的布局子类一个公共title
属性:
class MyFlowLayout : UICollectionViewFlowLayout {
var title = ""
// ...
}
Whoever uses this layout should set this property. For example, suppose this collection view is displaying the 50 U.S. states:
使用此布局的人应设置此属性。例如,假设此集合视图显示美国 50 个州:
func setUpFlowLayout(_ flow:UICollectionViewFlowLayout) {
flow.headerReferenceSize = CGSize(50,50)
flow.sectionInset = UIEdgeInsetsMake(0, 10, 10, 10)
(flow as? MyFlowLayout)?.title = "States" // *
}
We now come to a curious puzzle. Our layout has a title
property, the value of which needs to be communicated somehow to our MyTitleView instance. But when can that possibly happen? We are not in charge of instantiating MyTitleView; it happens automatically, when the collection view asks for the instance behind the scenes. There is no moment when the MyFlowLayout instance and the MyTitleView instance meet.
我们现在遇到一个奇怪的谜题。我们的布局有一个title
属性,它的值需要以某种方式传达给我们的 MyTitleView 实例。但这什么时候可能发生?我们不负责实例化 MyTitleView;当集合视图在幕后询问实例时,它会自动发生。MyFlowLayout 实例和 MyTitleView 实例没有相遇的时刻。
The solution is to use the layout attributes as a messenger. MyFlowLayout never meets MyTitleView, but it does create the layout attributes object that gets passed to the collection view to configure MyFlowLayout. So the layout attributes object is like an envelope. By subclassing UICollectionViewLayoutAttributes, we can include in that envelope any information we like — such as a title:
解决方案是使用布局属性作为信使。MyFlowLayout 从来没有遇到过 MyTitleView,但它确实创建了传递给集合视图以配置 MyFlowLayout 的布局属性对象。所以布局属性对象就像一个信封。通过继承 UICollectionViewLayoutAttributes,我们可以在该信封中包含我们喜欢的任何信息——例如标题:
class MyTitleViewLayoutAttributes : UICollectionViewLayoutAttributes {
var title = ""
}
There's our envelope! Now we rewrite our implementation of layoutAttributesForDecorationView
. When we instantiate the layout attributes object, we instantiate our subclass and set its title
property:
这是我们的信封!现在我们重写我们的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
}
Finally, in MyTitleView, we implement the apply(_:)
method. This will be called when the collection view configures the decoration view — with the layout attributes object as its parameter! So we pull out the title
and use it as the text of our label:
最后,在 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
}
}
}
It's easy to see how you might extend the example to make such label features as font and height customizable. Since we are subclassing UICollectionViewFlowLayout, some further modifications might also be needed to make room for the decoration view by pushing down the other elements. Also, technically, we should override isEqual(_:)
in MyTitleView to differentiate between different titles. All of that is left as an exercise for the reader.
很容易看出您可以如何扩展该示例以使诸如字体和高度之类的标签功能可自定义。由于我们是 UICollectionViewFlowLayout 的子类,因此可能还需要一些进一步的修改,通过向下推其他元素来为装饰视图腾出空间。此外,从技术上讲,我们应该isEqual(_:)
在 MyTitleView 中进行覆盖以区分不同的标题。所有这些都留给读者作为练习。
回答by dominikk
I got this working with a custom layout with the following:
我使用自定义布局进行了以下操作:
Create a subclass of UICollectionReusableView and for example add an UIImageView to it:
创建 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
Then in your controller in viewDidLoad register this subclass with the following code (replace code with your custom layout)
然后在 viewDidLoad 中的控制器中使用以下代码注册此子类(用您的自定义布局替换代码)
AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
[automationLayout registerClass:[AULYFloorPlanDecorationViewCell class] forDecorationViewOfKind:@"FloorPlan"];
In your custom layout then implement the following methods (or similar):
在您的自定义布局中,然后实现以下方法(或类似方法):
- (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;
}
There seems to be no documentation for it, but the following document got me on the right track: Collection View Programming Guide for iOS
似乎没有相关文档,但以下文档让我走上了正确的轨道:iOS 集合视图编程指南
UPDATE: It is probably better to subclass UICollectionReusableView for a decoration view instead of UICollectionViewCell
更新:为装饰视图而不是 UICollectionViewCell 子类化 UICollectionReusableView 可能更好
回答by Larry OBrien
Here's how to do it in MonoTouch:
以下是在 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...
}
With an end result similar to:
最终结果类似于:
回答by user1105951
In my case : I wanted to upgrade from UITableView to UICollectionView.
就我而言:我想从 UITableView 升级到 UICollectionView。
uitableview sections >>> supplementary views
uitableview 部分 >>> 补充视图
uitableview headerView >>> decoration view
uitableview headerView >>> 装饰视图
In my case I felt subclassing layout and do other stuff, it's "too much" for just simple "headerView" (decoration)
就我而言,我觉得子类化布局并做其他事情,对于简单的“headerView”(装饰)来说“太多了”
So my solution was just to create the headerview (not section) as first celland section 1as the first section ( section 0 was size of zero)
所以我的解决方案只是将 headerview(不是部分)创建为第一个单元格,将第1部分创建为第一部分(第 0 部分的大小为零)