Как создать эластичный заголовок для UICollectionViewCompositionalLayout с несколькими разделами?

Это моя первая реализация 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?

Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Начала с розового дизайна
Начала с розового дизайна
Pink Design - это система дизайна Appwrite с открытым исходным кодом для создания последовательных и многократно используемых пользовательских...
Шлюз в PHP
Шлюз в PHP
API-шлюз (AG) - это сервер, который действует как единая точка входа для набора микросервисов.
14 Задание: Типы данных и структуры данных Python для DevOps
14 Задание: Типы данных и структуры данных Python для DevOps
проверить тип данных используемой переменной, мы можем просто написать: your_variable=100
1
0
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел ответ, изменив код, который я нашел для 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) — это функция, которую вам нужно вызвать в вашем основном методе ViewControllerscrollViewDidScroll.

Зарегистрируйте вышеуказанный класс в своем 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().

Другие вопросы по теме