Подтвердить что ты не робот

Просмотр изображения UICollectionView

Кто-нибудь реализовал вид украшения для iOS 6 UICollectionView? Это невозможно найти любой учебник по внедрению вида оформления в Интернете. В основном в моем приложении у меня несколько разделов, и я просто хотел отобразить вид декорации за каждым разделом. Это должно быть просто реализовать, но мне не повезло. Это сводит меня с ума... Спасибо.

4b9b3361

Ответ 1

Здесь в стиле Swift (в формате Swift 3, Xcode 8) 6).


Представления декодирования не являются функцией UICollectionView; они по существу принадлежат к UICollectionViewLayout. Никакие методы UICollectionView (или методы делегирования или источника данных) не упоминают виды оформления. UICollectionView ничего не знает о них; он просто делает то, что сказано.

Чтобы предоставить любые виды декора, вам понадобится подкласс UICollectionViewLayout; этот подкласс может свободно определять свои собственные свойства и делегировать методы протокола, которые настраивают, как настроены его виды оформления, но это полностью зависит от вас.

Чтобы проиллюстрировать, я буду подклассифицировать UICollectionViewFlowLayout, чтобы наложить метку заголовка в верхней части прямоугольника содержимого представления коллекции. Это, вероятно, глупое использование декоративного вида, но оно прекрасно иллюстрирует основные принципы. Для простоты я начну с жесткого кодирования всего, предоставляя клиенту никакой возможности настроить любой аспект этого представления.

Существует четыре шага для реализации вида декорации в подклассе макета:

  • Определите подкласс UICollectionReusableView.

  • Зарегистрируйте подкласс UICollectionReusableView с макетом (а не видом коллекции), вызвав register(_:forDecorationViewOfKind:). Инициализатор макета - хорошее место для этого.

  • Внедрите 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.contains(decatts.frame) {
                    arr.append(decatts)
                }
        }
        return arr
}

Это работает! В верхней части представления коллекции появляется надпись метки надписи `` Testing '.


Теперь я покажу, как сделать ярлык настраиваемым. Вместо заголовка "Тестирование" мы разрешим клиенту установить свойство, определяющее заголовок. Я дам моему подклассу макета общедоступное свойство title:

class MyFlowLayout : UICollectionViewFlowLayout {
    var title = ""
    // ...
}

Тот, кто использует этот макет, должен установить это свойство. Например, предположим, что это представление коллекции отображает 50 состояний U.S.:

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, некоторые дополнительные модификации также могут потребоваться, чтобы освободить место для оформления, нажав на другие элементы. Кроме того, технически мы должны переопределить isEqual(_:) в MyTitleView, чтобы различать разные заголовки. Все это остается для упражнения читателем.

Ответ 2

Я получил эту работу с пользовательским макетом со следующим:

Создайте подкласс 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

UPDATE: возможно, лучше подклассифицировать UICollectionReusableView для вида оформления вместо UICollectionViewCell

Ответ 3

Здесь, как это сделать в 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

Ответ 4

если вы хотите добавить decorationview (Header/Footer) в collectionview

пожалуйста, проверьте эту ссылку в этом верхнем и нижнем колонтитулах, добавленных как DecorationView

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