Расширяемый UICollectionViewCell - работает только после прокрутки - PullRequest
0 голосов
/ 15 апреля 2019

Использование Swift-5.0, Xcode-10.2, iOS-12.2,

Короткий вопрос предлагается заранее - если вы хотите разобраться в проблеме, вы можете прочитать длинное описание ниже (.. Я пытался документировать как можно больше для этой сложной проблемы).

Короткий вопрос:

Можете ли вы сделать так, чтобы пользовательский UICollectionViewCell динамически изменял свою высоту (т. Е. Расширялся в высоту) после целевого действия из класса CollectionViewCell ??? (т.е. не с помощью метода sizeForItemAt соответствующего делегат-метода UICollectionViewController при снятии очереди, а скорее из самого пользовательского класса ячейки).

Если да, то как ?????????

Длинный описательный вопрос (посмотрите видео и подробное описание с вопросами ниже ...):

В приведенном ниже видео проиллюстрирована проблема, которая все еще существует в моем коде в отношении расширяемой ячейки UICollectionView.

enter image description here

Основной вопрос: почему ячейка CollectionView расширяется только после прокрутки пользователем до самого верха (а не до ??) ??

Как я узнал ( и опубликовал в этом ответе ), чтобы создать ячейку CollectionView-ячейки с авторазмером (то есть автоматически изменять ее высоту), вам необходимо реализовать две вещи:

  1. Убедитесь, что вы передали FlowLayout, установив свойство estimatedItemSize
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 1)

let vc = TestViewController(collectionViewLayout: collectionViewFlowLayout)
  1. Добавьте метод preferredLayoutAttributesFitting в свой пользовательский UICollectionViewCell (в моем примере, предоставив myContentHeight, который отслеживает, какой должна быть высота ячейки)
let myContentHeight = CGFloat(760)
// in my code-example, the 760 are the sum of 2 x 50 (labels) plus 5 x 100 (imageViews) plus 8 x 20 (spacing)

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    setNeedsLayout()
    layoutIfNeeded()
    var newFrame = layoutAttributes.frame
    // myContentHeight corresponds to the total height of your custom UICollectionViewCell
    newFrame.size.height = ceil(myContentHeight)
    layoutAttributes.frame = newFrame
    return layoutAttributes
}

.

После 1. и 2. содержимое ячейки увеличивается. Но, к сожалению, как показано в видео выше - общая ячейка CollectionView (желтый фон) расширяется только после того, как пользователь прокручивает все до самого верха!

Я почти уверен, что только после прокрутки вверх снова вызывается метод preferredLayoutAttributesFitting.

Вопрос в следующем: КАК ЭТО ПРИЧИНА preferredLayoutAttributesFitting ОТ НАЗВАНИЯ (т. Е. Кроме прокрутки наверх ???)

Или есть другой способ заставить ячейку регулировать свою высоту?

Спасибо за любую помощь в этом.

Вот весь мой код:

UICollectionViewController настроен так:

class TestViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    let cellId = "cellID"

    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.backgroundColor = .yellow
        collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 1
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
        return cell
    }

//    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//        return .init(width: view.frame.width, height: 4000)
//    }

    init() {
        let collectionViewFlowLayout = UICollectionViewFlowLayout()
//        collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 4000)
        collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        super.init(collectionViewLayout: collectionViewFlowLayout)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }   
}

Пользовательская ячейка CollectionView выглядит следующим образом:

import UIKit

class MyCollectionViewCell: UICollectionViewCell {

    let titleLabel1 = UILabel(text: "Title 123", font: .boldSystemFont(ofSize: 30))
    let titleLabel2 = UILabel(text: "Testing...", font: .boldSystemFont(ofSize: 15))
    let imgView1 = UIImageView(image: nil)
    let imgView2 = UIImageView(image: nil)
    var imgView2Enlarged: Bool = false
    let imgView3 = UIImageView(image: nil)
    let imgView4 = UIImageView(image: nil)
    let imgView5 = UIImageView(image: nil)
    var stackView = UIStackView()

    let imageView: UIImageView = {
        let imgView = UIImageView()

        imgView.backgroundColor = .green
        return imgView
    }()

    var myContentHeight: CGFloat = 0.0
    var height2ShrunkConstraint: NSLayoutConstraint!
    var height2ExpandedConstraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)

        backgroundColor = .yellow
        titleLabel1.constrainHeight(constant: 50)
        titleLabel1.constrainWidth(constant: 250)
        titleLabel2.constrainHeight(constant: 50)
        titleLabel2.constrainWidth(constant: 250)

        imgView1.constrainHeight(constant: 100)
        imgView1.constrainWidth(constant: 200)
        imgView1.backgroundColor = .green

        translatesAutoresizingMaskIntoConstraints = false
        self.height2ShrunkConstraint = imgView2.heightAnchor.constraint(equalToConstant: 100)
        self.height2ShrunkConstraint.isActive = true
        self.height2ExpandedConstraint = imgView2.heightAnchor.constraint(equalToConstant: 300)
        self.height2ExpandedConstraint.isActive = false

        // imgView2.constrainHeight(constant: 100)
        imgView2.constrainWidth(constant: 200)
        imgView2.backgroundColor = .green
        imgView3.constrainHeight(constant: 100)
        imgView3.constrainWidth(constant: 200)
        imgView3.backgroundColor = .green
        imgView4.constrainHeight(constant: 100)
        imgView4.constrainWidth(constant: 200)
        imgView4.backgroundColor = .green
        imgView5.constrainHeight(constant: 100)
        imgView5.constrainWidth(constant: 200)
        imgView5.backgroundColor = .green

        myContentHeight = 760
        stackView = UIStackView(arrangedSubviews: [UIView(), titleLabel1, imgView1, titleLabel2, imgView2, imgView3, imgView4, imgView5, UIView()])
        addSubview(stackView)
//        stackView.constrainHeight(constant: myContentHeight)
//        stackView.constrainWidth(constant: 250)
        stackView.anchor(top: safeAreaLayoutGuide.topAnchor, leading: leadingAnchor, bottom: nil, trailing: trailingAnchor)
        stackView.spacing = 20
        stackView.axis = .vertical
        stackView.alignment = .center
        stackView.distribution = .fill

        let gesture = UITapGestureRecognizer(target: self, action: #selector(imgTapped))
        gesture.numberOfTapsRequired = 1
        imgView2.isUserInteractionEnabled = true
        imgView2.addGestureRecognizer(gesture)
    }

    @objc fileprivate func imgTapped(gesture: UITapGestureRecognizer) {
        if !imgView2Enlarged {
            UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut
                , animations: {
                    self.imgView2Enlarged = true
                    self.myContentHeight += 200

                    self.height2ShrunkConstraint.isActive = false
                    self.height2ExpandedConstraint.isActive = true

                    self.layoutIfNeeded()
            })
        } else {
            UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
                self.imgView2Enlarged = false

                self.height2ExpandedConstraint.isActive = false
                self.height2ShrunkConstraint.isActive = true

                self.myContentHeight -= 200
                self.layoutIfNeeded()
            })
        }
    }

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        setNeedsLayout()
        layoutIfNeeded()
        updateConstraintsIfNeeded()
        var newFrame = layoutAttributes.frame
        // myContentHeight corresponds to the total height of your custom UICollectionViewCell
        newFrame.size.height = ceil(myContentHeight)
        layoutAttributes.frame = newFrame
        return layoutAttributes
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Чтобы абстрагироваться от привязки и определения высот ограничений autolayout для компонентов ячейки (то есть набора меток и изображений, заполняющих ячейку), я использовал следующее расширение UIView ...:

(Расширение было опубликовано Брайан Вонг - см. видео ссылка )

// Reference Video: https://youtu.be/iqpAP7s3b-8
extension UIView {

    @discardableResult
    func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints {

        translatesAutoresizingMaskIntoConstraints = false
        var anchoredConstraints = AnchoredConstraints()

        if let top = top {
            anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top)
        }

        if let leading = leading {
            anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
        }

        if let bottom = bottom {
            anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
        }

        if let trailing = trailing {
            anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
        }

        if size.width != 0 {
            anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width)
        }

        if size.height != 0 {
            anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height)
        }

        [anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach{ $0?.isActive = true }

        return anchoredConstraints
    }

    func fillSuperview(padding: UIEdgeInsets = .zero) {
        translatesAutoresizingMaskIntoConstraints = false
        if let superviewTopAnchor = superview?.topAnchor {
            topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true
        }

        if let superviewBottomAnchor = superview?.bottomAnchor {
            bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: -padding.bottom).isActive = true
        }

        if let superviewLeadingAnchor = superview?.leadingAnchor {
            leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true
        }

        if let superviewTrailingAnchor = superview?.trailingAnchor {
            trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: -padding.right).isActive = true
        }
    }

    func centerInSuperview(size: CGSize = .zero) {
        translatesAutoresizingMaskIntoConstraints = false
        if let superviewCenterXAnchor = superview?.centerXAnchor {
            centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true
        }

        if let superviewCenterYAnchor = superview?.centerYAnchor {
            centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true
        }

        if size.width != 0 {
            widthAnchor.constraint(equalToConstant: size.width).isActive = true
        }

        if size.height != 0 {
            heightAnchor.constraint(equalToConstant: size.height).isActive = true
        }
    }

    func centerXInSuperview() {
        translatesAutoresizingMaskIntoConstraints = false
        if let superViewCenterXAnchor = superview?.centerXAnchor {
            centerXAnchor.constraint(equalTo: superViewCenterXAnchor).isActive = true
        }
    }

    func centerYInSuperview() {
        translatesAutoresizingMaskIntoConstraints = false
        if let centerY = superview?.centerYAnchor {
            centerYAnchor.constraint(equalTo: centerY).isActive = true
        }
    }

    func constrainWidth(constant: CGFloat) {
        translatesAutoresizingMaskIntoConstraints = false
        widthAnchor.constraint(equalToConstant: constant).isActive = true
    }

    func constrainHeight(constant: CGFloat) {
        translatesAutoresizingMaskIntoConstraints = false
        heightAnchor.constraint(equalToConstant: constant).isActive = true
    }
}
...