Авторазмер ячеек в UICollectionView (все в коде) - PullRequest
0 голосов
/ 14 апреля 2019

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

В моем коде есть проблема при попытке добиться "автоматического изменения размера" ячеек в UICollectionView (используя UICollectionViewFlowLayout в качестве макета).

Высота ячейки зависит от содержимого (и неизвестна заранее) - поэтому я пытаюсь получить высоту ячейки для автоматического изменения размера ("ширина", которая мне сейчас не нужна, и могуНапример, установите значение frame.width).

Даже несмотря на то, что в приведенном ниже примере я использую только одну большую ячейку UICollectionView, я все еще хотел бы поддерживать «снятие очереди» с ячеек, поскольку позжебудет гораздо больше ячеек, которые должны быть заполнены большим содержимым.Поэтому, чтобы сделать этот пример более простым, я сохраню numberOfItemsInSection в 1.

Более того, для приведенного ниже примера каждый пользовательский CollectionViewCell заполнен вертикальным StackView (вертикально добавляя пару меток иImageViews).StackView здесь не важен, но я сделал это в качестве быстрого примера.Опять же, содержимое пользовательского CollectionViewCell изменится позже (особенно это изменится на зависящую от содержимого высоту).Содержимое ячейки с фиксированной высотой в приведенном ниже коде приведено исключительно в качестве примера ...

Но я все же хотел бы извлечь из этого вопрос о том, как заставить CollectionViewCell автоматически изменять свою высоту в соответствии ск содержимому (будь то фиксированная высота, как в примере ниже, или динамическая высота на основе содержимого, как в будущей реализации) ???

Я получаю следующую ошибку ограничения:

Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want. 
Try this: 
  (1) look at each constraint and try to figure out which you don't expect; 
  (2) find the code that added the unwanted constraint or constraints and fix it. 

Все работает, если я добавлю ячейке очень большую фиксированную высоту, используя метод sizeForItemAt внутри моего UICollectionViewController.(т. е. с «очень большим» я имею в виду, что намного больше, чем высота ячейки получается, когда происходит вытеснение ячейки).

Но, как объяснено выше, я не хочу устанавливать фиксированное значение высоты ячейки(используя метод UICollectionViewController sizeForItemAt - но скорее достигните желаемого автоматического изменения размера.

Чтобы автоматически изменить размер ячейки, я попытался сделать следующее:

Подход A)

Использовать collectionViewFlowLayout.estimatedItemSize = CGSize(...)

(и не использовать метод sizeForItemAt)

-> Опять то же самое: если предполагаемый размер задан достаточно большим, ошибки не возникает,Если я установлю его слишком маленьким, то получу ту же ошибку ограничения ...

Подход B)

Использование collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

(и использовать или не использовать sizeForItemAt метод - не имеет значения)

-> Опять та же ошибка: как только я начинаю прокрутку на ячейке UICollectionView, она выдает ту же ошибку Constraint-error...

Несколько обнадеживающим является наблюдение, что использование UICollectionViewFlowLayout.automaticSize заставляет приложение работать по желанию (за исключением того, что вышеупомянутая ошибка все еще выдается - но, как ни странно, приложение продолжает каким-то образом работать в любом случае).Для меня неприемлемо, что приложение работает и выдается ошибка.Вопрос в том, как избавиться от Constraint-error ??

Вот весь код, который я использую:

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")
    }   
}

Вот CustomCollectionViewCell:

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)
    let imgView3 = UIImageView(image: nil)
    let imgView4 = UIImageView(image: nil)
    let imgView5 = UIImageView(image: nil)


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

        imgView.backgroundColor = .green
        return imgView
    }()

    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
        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

        let stackView = UIStackView(arrangedSubviews: [titleLabel1, imgView1, titleLabel2, imgView2, imgView3, imgView4, imgView5])

        addSubview(stackView)
        stackView.anchor(top: safeAreaLayoutGuide.topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor)
        stackView.spacing = 20
        stackView.axis = .vertical
        stackView.alignment = .center
    }

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

Необходимые расширения определены следующим образом:

extension UILabel {
    convenience init(text: String, font: UIFont) {
        self.init(frame: .zero)
        self.text = text
        self.font = font
        self.backgroundColor = .red
    }
}

extension UIImageView {
    convenience init(cornerRadius: CGFloat) {
        self.init(image: nil)
        self.layer.cornerRadius = cornerRadius
        self.clipsToBounds = true
        self.contentMode = .scaleAspectFill
    }
}

Чтобы абстрагироваться от привязки и определения высоты ограничений 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
    }
}

1 Ответ

0 голосов
/ 14 апреля 2019

Наконец, после еще нескольких копаний, я нашел решение:

Для автоматического изменения размера пользовательского UICollectionViewCell важны четыре вещи:

  1. при создании вашего экземпляра UICollectionViewController, убедитесь, что вы передали FlowLayout, установив свойство estimatedItemSize, какое-то значение с обоснованным предположением (в моем случае высота = 1 [так как неизвестно])
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 1)

let vc = TestViewController(collectionViewLayout: collectionViewFlowLayout)

.

  1. создать свойство внутри пользовательского UICollectionViewCell, которое отслеживает высоту ячейки

(т. Е. Внутри вашего класса UICollectionViewCell вы всегда будете знать, какова высота ячейки (даже если впоследствии она будет динамически изменяться). Опять же, я хотел избавиться от того факта, что мне нужно было определить эту высоту уже в UICollectionViewController's sizeForItemAt метод перед удалением из ячейки)

  1. Не забудьте добавить метод preferredLayoutAttributesFitting в свой пользовательский UICollectionViewCell:
let myContentHeight = CGFloat(720)
// in my above code-example, the 720 are the sum of 2 x 50 (labels) plus 5 x 100 (imageViews) plus 6 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. Конечно, всякий раз, когда ваш пользовательский CollectionViewCell динамически меняет свою высоту, вам также необходимо снова изменить свойство myContentHeight ...

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

...