web-dev-qa-db-ja.com

UICollectionViewデコレーションビュー

IOS 6 UICollectionViewの装飾ビューを実装している人はいますか? Web上で装飾ビューを実装するためのチュートリアルを見つけることは不可能です。基本的に私のアプリには複数のセクションがあり、各セクションの背後に装飾ビューを表示したかっただけです。これは簡単に実装できるはずですが、私には運がありません。これは私を夢中にさせています...ありがとう。

34
vtruong

コレクションビューレイアウトデコレーションビューチュートリアル in Swift(これはSwift 3、Xcode 8シード6)です。


装飾ビューはUICollectionView機能ではありません。これらは基本的にUICollectionViewLayoutに属します。 UICollectionViewメソッド(またはデリゲートまたはデータソースメソッド)は、装飾ビューに言及しません。 UICollectionViewはそれらについて何も知りません。それは単に言われたことをします。

装飾ビューを提供するには、UICollectionViewLayoutサブクラスが必要です。このサブクラスは、独自のプロパティを自由に定義し、装飾ビューの構成方法をカスタマイズするプロトコルメソッドを委任できますが、それは完全にユーザー次第です。

説明のために、UICollectionViewFlowLayoutをサブクラス化して、コレクションビューのコンテンツの四角形の上部にタイトルラベルを課します。これはおそらく装飾ビューのばかげた使用方法ですが、基本原理を完全に示しています。簡単にするために、全体をハードコーディングすることから始めて、このビューのあらゆる側面をクライアントがカスタマイズできないようにします。

レイアウトサブクラスに装飾ビューを実装するには、4つの手順があります。

  1. UICollectionReusableViewサブクラスを定義します。

  2. register(_:forDecorationViewOfKind:)を呼び出して、UICollectionReusableViewサブクラスをレイアウト(notコレクションビュー)に登録します。これを行うには、レイアウトの初期化子が適しています。

  3. layoutAttributesForDecorationView(ofKind:at:)を実装して、UICollectionReusableViewを配置するレイアウト属性を返します。レイアウト属性を作成するには、init(forDecorationViewOfKind:with:)を呼び出して属性を設定します。

  4. 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」の代わりに、クライアントがタイトルを決定するプロパティを設定できるようにします。レイアウトサブクラスにpublic 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(_:)をオーバーライドして、タイトルを区別する必要があります。これらはすべて、読者の演習として残されています。

66
matt

私はこれを次のカスタムレイアウトで動作させました:

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

更新:UICollectionViewCellではなく、装飾ビューのUICollectionReusableViewをサブクラス化することをお勧めします

21
dominikk

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

5
Larry OBrien

私の場合:UITableViewからUICollectionViewにアップグレードしたかった。

uitableviewセクション>>>補足ビュー

uitableview headerView >>>装飾ビュー

私の場合、レイアウトをサブクラス化することや他のことをすることを感じました。単純な「headerView」(装飾)では「多すぎる」

だから私の解決策は、ヘッダービュー(セクションではなく)を作成することでした最初のセルとしてとセクション1最初のセクションとして(セクション0はゼロのサイズでした)

0
user1105951