Кто-нибудь реализовал представление оформления для iOS 6 UICollectionView? Невозможно найти какое-либо руководство по реализации декоративного представления в Интернете. По сути, в моем приложении у меня есть несколько разделов, и я просто хотел отобразить оформление за каждым разделом. Это должно быть просто реализовать, но мне не повезло. Это сводит меня с ума... Спасибо.
UICollectionView Представление украшения
Ответы (4)
Вот руководство по оформлению макета представления коллекции в Swift (это Swift 3, Xcode 8 seed 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.intersects(decatts.frame) {
arr.append(decatts)
}
}
return arr
}
Это работает! Метка заголовка с надписью «Тестирование» появляется в верхней части представления коллекции.
Теперь я покажу, как сделать метку настраиваемой. Вместо заголовка «Тестирование» мы позволим клиенту установить свойство, определяющее заголовок. Я дам моему подклассу макета общедоступное свойство 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, могут также потребоваться некоторые дополнительные модификации, чтобы освободить место для декоративного представления, сдвинув другие элементы. Кроме того, технически мы должны переопределить isEqual(_:)
в MyTitleView, чтобы различать разные заголовки. Все это остается в качестве упражнения для читателя.
if rect.contains(decatts.frame)
вы должны использовать if rect.intersects(decatts.frame)
.
- person Marián Černý; 13.08.2018
layoutAttributesForDecorationView
, ни layoutAttributesForItem
никогда не звонят. Вызывается только layoutAttributesForElements
. Что может быть причиной? Уже несколько часов бьюсь с этим, но бесполезно.
- person Adeel; 12.09.2018
Я получил эту работу с пользовательским макетом со следующим:
Создайте подкласс 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
ОБНОВЛЕНИЕ: вероятно, лучше создать подкласс UICollectionReusableView для декоративного представления вместо UICollectionViewCell.
[allAttributes addObject:[self layoutAttributesForDecorationViewOfKind:@"FloorPlan" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]];
(и вы можете добавить дополнительные для разделов 2, 3, 4 и т. д. Затем вам нужно обязательно установить атрибуты макета, чтобы границы были правильными для этого раздела. Чтобы узнать количество разделов и количество элементов, которые они содержат, вы должны иметь доступ к self.collectionView.dataSource
- person dominikk; 11.10.2012
UICollectionViewLayout
для использования декоративных представлений? Можно ли это сделать из FlowLayout?
- person Drew C; 26.03.2013
Вот как это сделать в 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...
}
С конечным результатом, похожим на:
В моем случае: я хотел перейти с UITableView на UICollectionView.
разделы uitableview >>> дополнительные виды
uitableview headerView >>> декоративный вид
В моем случае я чувствовал макет подкласса и делал другие вещи, это «слишком много» для простого «headerView» (украшение)
Поэтому мое решение состояло в том, чтобы просто создать заголовок (не раздел) в качестве первой ячейки и раздел 1 в качестве первого раздела (раздел 0 имел нулевой размер)