Использование 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
}
}