xcode 如何为UICollectionViewCompositionalLayout创建一个具有多个部分的可伸缩标题?

nfeuvbwi  于 2022-11-26  发布在  其他
关注(0)|答案(1)|浏览(129)

这是我第一次实现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的弹性页眉?

rt4zxlrg

rt4zxlrg1#

我通过修改StretchyTableHeaderView的一些代码找到了答案。在添加下面的代码之前,我将尝试简单地解释我所做的事情。因此,首先我只是简单地创建了CollectionViewReusableView,就像为CollectionViews创建的任何SupplementaryView一样。实际上,我只是为stretchy TableViewHeader找到了这段代码。我只是将它转换为UICollectionReusableView。但是,此标题视图包含scrollViewDidScroll函数,该函数操作containerView和imageView的底部约束,还操作imageView的高度,使其随滚动视图。
然后你只需将它注册到你的collectionView中,将它作为supplementaryView传递给First Section,并在创建布局时给予它一个高度。最后在scrollViewDidScroll的委托方法中,寻找你的补充视图,如果找到了,就在你的头中调用scrollViewDidScroll函数。
这是StretchHeaderCollectionResultableView类别:

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.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()中调用上述函数。

相关问题