Кто-нибудь реализовал вид украшения для iOS 6 UICollectionView? Это невозможно найти любой учебник по внедрению вида оформления в Интернете. В основном в моем приложении у меня несколько разделов, и я просто хотел отобразить вид декорации за каждым разделом. Это должно быть просто реализовать, но мне не повезло. Это сводит меня с ума... Спасибо.
Просмотр изображения UICollectionView
Ответ 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...
}
С конечным результатом, аналогичным:
Ответ 4
если вы хотите добавить decorationview (Header/Footer) в collectionview
пожалуйста, проверьте эту ссылку в этом верхнем и нижнем колонтитулах, добавленных как DecorationView