Ответ 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, чтобы различать разные заголовки. Все это остается для упражнения читателем.