Это моя первая реализация UICollectionViewCompositionalLayout
, и я не понимаю, как реализовать здесь эластичный заголовок. Прямо сейчас я только что изменил функцию dataSource.supplementaryViewProvider
, чтобы включить свой собственный HeaderView, но я не знаю, как сделать так, чтобы он прикреплялся сверху.
Я нашел код для других типов макетов collectionView, но они не работают с UICollectionViewCompositionalLayout
. Для других макетов я обнаружил, что мне нужно переопределить это override func layoutAttributesForElements(in rect: CGRect)
, но где и как? Я хотел бы знать метод с нуля. Ниже мой метод, который вообще не работает с UICollectionViewCompositionalLayout
.
Вот как я создаю свой заголовок:
class StretchyCollectionHeaderView: UICollectionReusableView {
static let reuseIdentifier = "stretchyCollectionHeaderView-reuse-identifier"
let imageView: UIImageView = {
let iv = UIImageView(image: UIImage(named: "HeaderHomePage"))
iv.contentMode = .scaleAspectFill
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
// custom code for layout
backgroundColor = .red
addSubview(imageView)
imageView.fillSuperview()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Я использую UICollectionReusableView
вместо UIView
, потому что все заголовки разделов передаются как UICollectionReusableView
. Я использую расширение, которое соединит нижнюю часть imageView с представлением заголовка и для других ограничений, я не включил это, потому что я думаю, что оно даже не используется, к чему я вернусь в конце объяснения.
Это мой макет для CollectionViewLayout с эластичным заголовком:
class StretchyHeaderLayout: UICollectionViewCompositionalLayout {
// we want to modify the attributes of our header component somehow
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)
layoutAttributes?.forEach({ (attributes) in
if attributes.representedElementKind == UICollectionView.elementKindSectionHeader && attributes.indexPath.section == 0 {
guard let collectionView = collectionView else { return }
let contentOffsetY = collectionView.contentOffset.y
print(contentOffsetY)
if contentOffsetY > 0 {
return
}
let width = collectionView.frame.width
let height = attributes.frame.height - contentOffsetY
// header
attributes.frame = CGRect(x: 0, y: contentOffsetY, width: width, height: height)
}
})
return layoutAttributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Затем я просто регистрирую все ячейки и дополнительные представления. Затем я настраиваю свой collectionViewLayout следующим образом:
func generateLayout() -> UICollectionViewLayout {
let layout = StretchyHeaderLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let isWideView = layoutEnvironment.traitCollection.horizontalSizeClass == .regular
let sectionLayoutKind = Section.allCases[sectionIndex]
switch (sectionLayoutKind) {
case .locationTab: return self.generateLocationLayout(isWide: isWideView)
case .selectCategory: return self.generateCategoriesLayout()
case .valueAddedServices: return self.generatValueAddedServicesLayout(isWide: isWideView)
}
}
return layout
}
Я устанавливаю layout = StretchyHeaderLayout
, так как это был единственный возможный способ добавить макет эластичного заголовка.
И, наконец, вот как я настраиваю заголовки разделов:
dataSource.supplementaryViewProvider = { (
collectionView: UICollectionView,
kind: String,
indexPath: IndexPath) -> UICollectionReusableView? in
if indexPath.section == 0 {
guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: StretchyCollectionHeaderView.reuseIdentifier,
for: indexPath) as? StretchyCollectionHeaderView else { fatalError("Cannot create header view") }
supplementaryView.imageView.image = UIImage(named: "HeaderHomePage")
return supplementaryView
}
else {
guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: HeaderView.reuseIdentifier,
for: indexPath) as? HeaderView else { fatalError("Cannot create header view") }
supplementaryView.label.text = Section.allCases[indexPath.section].rawValue
return supplementaryView
}
}
Здесь я просто использую StretchyCollectionHeaderView
для первого раздела, а для других я использую другой HeaderView
, который просто содержит метку.
Я думаю, что это происходит из-за вышеуказанной функции, она просто устанавливает вид заголовка с StreatchyCollectionHeaderView
для первого раздела, но не получает доступ к какому-либо коду внутри StretchyHeaderLayout
и, следовательно, не придерживается вершины.
В отличие от UITableView
, мы не можем прикрепить заголовок CollectionView вместо добавления заголовка раздела или, в моем случае, заголовка раздела с изображением для первого раздела и прикрепить сверху?
Как правильно создать эластичный заголовок для UICollectionViewCompositionalLayout
?
Я нашел ответ, изменив код, который я нашел для StretchyTableHeaderView
. Я попытаюсь вкратце объяснить, что я сделал, прежде чем добавлять код ниже. Итак, сначала я просто создал CollectionViewReusableView
как для любого дополнительного представления, которое вы создаете для CollectionViews
. На самом деле я только что нашел этот код для растягивающегося TableViewHeader, я только что преобразовал его в UICollectionReusableView
. Но это представление заголовка содержит функцию scrollViewDidScroll, которая манипулирует нижними ограничениями для вашего containerView и imageView, а также манипулирует высотой imageView, чтобы она увеличивалась со смещением scrollView.
После этого вы просто регистрируете это в своем collectionView
, передаете это как supplementaryView
для первого раздела и задаете ему высоту при создании макета. И, наконец, в методе делегата scrollViewDidScroll
найдите дополнительное представление и, если оно будет найдено, просто вызовите функцию scrollViewDidScroll внутри вашего заголовка.
Это класс StretchHeaderCollectionResulableView:
final class StretchyCollectionHeaderView: UICollectionReusableView {
static let reuseIdentifier = "stretchy-homePage-header-view-reuse-identifier"
public let imageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
return imageView
}()
private var imageViewHeight = NSLayoutConstraint()
private var imageViewBottom = NSLayoutConstraint()
private var containerView = UIView()
private var containerViewHeight = NSLayoutConstraint()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
createViews()
setViewConstraints()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
/// Create Subviews
private func createViews() {
addSubview(containerView)
containerView.addSubview(imageView)
}
/// Setup View Constraints
func setViewConstraints() {
NSLayoutConstraint.activate([
widthAnchor.constraint(equalTo: containerView.widthAnchor),
centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
heightAnchor.constraint(equalTo: containerView.heightAnchor)
])
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = translatesAutoresizingMaskIntoConstraints
containerViewHeight = containerView.heightAnchor.constraint(equalTo: self.heightAnchor)
containerViewHeight.isActive = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageViewBottom = imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
imageViewBottom.isActive = true
imageViewHeight = imageView.heightAnchor.constraint(equalTo: containerView.heightAnchor)
imageViewHeight.isActive = true
}
/// Notify View of scroll change from container
public func scrollviewDidScroll(scrollView: UIScrollView) {
containerViewHeight.constant = scrollView.contentInset.top
let offsetY = -(scrollView.contentOffset.y + scrollView.contentInset.top)
containerView.clipsToBounds = offsetY <= 0
imageViewBottom.constant = offsetY >= 0 ? 0 : -offsetY / 2
imageViewHeight.constant = max(offsetY + scrollView.contentInset.top, scrollView.contentInset.top)
}
}
public func scrollviewDidScroll(scrollView: UIScrollView)
— это функция, которую вам нужно вызвать в вашем основном методе ViewController
scrollViewDidScroll
.
Зарегистрируйте вышеуказанный класс в своем collectionView:
collectionView.register(StretchyCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: StretchyCollectionHeaderView.reuseIdentifier)
Добавьте это в заголовок раздела при создании макета для collectionView:
func generateFirstSectionLayout(isWide: Bool) -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
// Set header properties here
let headerSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(isWide ? 2/4 : 2/3))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerSize,
elementKind: HomePageViewController.sectionHeaderElementKind,
alignment: .top)
let section = NSCollectionLayoutSection(group: group)
section.boundarySupplementaryItems = [sectionHeader]
return section
}
Добавьте данные в заголовок:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if indexPath.section == 0 {
guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: StretchyCollectionHeaderView.reuseIdentifier,
for: indexPath) as? StretchyCollectionHeaderView else { fatalError("Cannot create header view") }
supplementaryView.imageView.image = UIImage(named: "HeaderHomePage")
return supplementaryView
}
else {
guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: HomePageAutoxHeaderView.reuseIdentifier,
for: indexPath) as? HeaderView else { fatalError("Cannot create header view") }
return supplementaryView
}
}
Добавьте в первый раздел, а затем, наконец, вызовите это в своем ViewController:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let header = homePageCollectionView.supplementaryView(forElementKind: HomePageViewController.sectionHeaderElementKind, at: IndexPath(item: 0, section: 0)) as? StretchyCollectionHeaderView {
header.scrollviewDidScroll(scrollView: homePageCollectionView)
}
}
Если ваш заголовок растягивается только после того, как вы начнете прокручивать страницу, вызовите также вышеуказанную функцию в viewDidAppear()
.