大家下午好。我有一个单元格高度可变的集合。滚动集合时,内容的高度与单元格的高度不匹配。告诉我如何解决这个问题?
这就是我创建集合的方式。
// MARK: - UI Fabric.
private extension CollectionViewController {
func createCollectionView() -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 16
layout.minimumLineSpacing = 16
layout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.showsVerticalScrollIndicator = false
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}
}
这是代表或设置大小单元格。
// MARK: - CollectionView Flow Layout.
extension CollectionViewController: UICollectionViewDelegateFlowLayout {
/// Setting cell sizes.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
delegateSettingCell.calculateCellSize(screenWidth: view.bounds.width, index: indexPath.row)
}
}
这里我正在计算像元大小。
func calculateCellSize(screenWidth: CGFloat, index: Int) -> CGSize {
let item = modelForDisplay[index].addonDetail.count
let heightCell = CGFloat((item * 44) + 44)
let sizeCellWidth = (view.bounds.width - 32)
return CGSize(width: sizeCellWidth, height: heightCell)
}
final class AddonCreatorCell: UICollectionViewCell {
// MARK: - Dependencies
var delegate: IHandlerAddonCreatorCellDelegate?
// MARK: - Public properties
static var reuseIdentifier: String = "AddonCreatorCell.cell"
// MARK: - Private properties
private lazy var textFields: [UITextField] = []
private lazy var viewDie = createView()
private lazy var gradientViewDie = createGradient(GradientColors.yellowGradient)
private lazy var labelTitle = createUILabel()
private lazy var switchShow = createSwitch()
private lazy var vStack = createStack()
private lazy var headerStack = createStack()
// MARK: - Initializator
override init(frame: CGRect) {
super.init(frame: frame)
addUIView()
setupConfiguration()
setupLayout()
}
convenience init(handlerAddonCreatorCellDelegate: IHandlerAddonCreatorCellDelegate?) {
self.init(frame: CGRect.zero)
self.delegate = handlerAddonCreatorCellDelegate
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Public methods
func reloadFrame(size: CGSize) {
self.gradientViewDie.frame.size = size
}
func reloadData(model: CreateAddon) {
// Here I delete unnecessary UIStask with content
vStack.subviews.forEach {
if $0 != headerStack {
$0.removeFromSuperview()
}
}
labelTitle.text = "\(model.title) \(model.addonDetail.count)"
model.isActive ? activeSwitch() : deactivateSwitch()
switchShow.setOn(model.isActive, animated: false)
for (index, element) in model.addonDetail.enumerated() {
createContent(item: element, index: index)
}
}
private func createContent(item: AddonDetail, index: Int) {
let convertToString = String(item.count)
let label = createUILabel()
let textField = createTextField(id: index, text: convertToString)
let hStack = createStack()
label.text = item.title
textFields.append(textField)
textField.delegate = self
textField.addTarget(self, action: #selector(self.myTextFieldChanged(_:)), for: .editingChanged)
hStack.axis = .horizontal
hStack.alignment = .center
hStack.distribution = .fillProportionally
hStack.addArrangedSubview(label)
hStack.addArrangedSubview(textField)
vStack.addArrangedSubview(hStack)
addConstraintTextField(textField)
}
}
您不需要每次都“重新构建”可重复使用的单元...
将堆栈视图视为“行”
如果您知道任何单个单元格将拥有的 max 行数,请在单元格的 init 中创建它们。
然后,当您在
cellForItemAt
中设置数据时,根据需要显示/隐藏“行”。像这样的东西:
// show used "rows" and hide unused "rows"
for (i, v) in vStack.arrangedSubviews.enumerated() {
v.isHidden = !(i < model.addons.count)
}
如果您不知道潜在的 max 行数,请根据需要创建它们(同样,在
cellForItemAt
中设置单元格数据时:
// if we have fewer "rows" than addons, create new "rows"
while vStack.arrangedSubviews.count < model.addons.count {
// add a new textfield & switch
vStack.addArrangedSubview(...)
}
// show used "rows" and hide unused "rows"
for (i, v) in vStack.arrangedSubviews.enumerated() {
v.isHidden = !(i < model.addons.count)
}
当排列子视图被隐藏时,它仍然存在,但堆栈视图将其视为不存在。
这是一个完整的示例 - 大致基于您问题中的代码:
struct Addon {
var text: String = ""
var selected: Bool = false
}
struct CreateAddon {
var title: String = ""
var addons: [Addon] = []
}
class CollectionViewController: UIViewController {
var myData: [CreateAddon] = []
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView = createCollectionView()
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
collectionView.register(AddonCreatorCell.self, forCellWithReuseIdentifier: AddonCreatorCell.reuseIdentifier)
collectionView.dataSource = self
collectionView.delegate = self
view.backgroundColor = .init(red: 0.108, green: 0.379, blue: 0.129, alpha: 1.0)
collectionView.backgroundColor = .init(red: 0.108, green: 0.379, blue: 0.129, alpha: 1.0)
// create some sample data
let numRows: [Int] = [3, 5, 2, 6, 4, 3, 3, 4, 7, 5, 3, 4, 6, 5, 6, 2]
for (row, n) in numRows.enumerated() {
var theseAddons: [Addon] = []
for i in 0..<n {
let a: Addon = Addon(text: "User Text \(i)", selected: false)
theseAddons.append(a)
}
let cr = CreateAddon(title: "Addon Title \(row)", addons: theseAddons)
myData.append(cr)
}
}
}
extension CollectionViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: AddonCreatorCell.reuseIdentifier, for: indexPath) as! AddonCreatorCell
c.fillData(model: myData[indexPath.item])
// cell's "die" view width -- it has 8-points "spacing" on each side
c.wConstraint.constant = collectionView.frame.width - 16.0
return c
}
}
private extension CollectionViewController {
func createCollectionView() -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = .init(width: 300.0, height: 50.0)
layout.minimumInteritemSpacing = 16
layout.minimumLineSpacing = 16
layout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.showsVerticalScrollIndicator = false
return collectionView
}
}
class TextFieldSwitch: UIView {
private let theTextField = UITextField()
private let theSwitch = UISwitch()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
let sv = UIStackView()
sv.spacing = 8
sv.addArrangedSubview(theTextField)
sv.addArrangedSubview(theSwitch)
self.addSubview(sv)
sv.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
sv.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
sv.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
sv.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0.0),
sv.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0),
])
theTextField.borderStyle = .bezel
// make sure the switch doesn't stretch
theSwitch.setContentHuggingPriority(.required, for: .horizontal)
}
public func fillData(d: Addon) {
theTextField.text = d.text
theSwitch.isOn = d.selected
}
}
final class AddonCreatorCell: UICollectionViewCell {
// we'll use this to make the cells the width of the collection view
public var wConstraint: NSLayoutConstraint!
// MARK: - Dependencies
//var delegate: IHandlerAddonCreatorCellDelegate?
// MARK: - Public properties
static var reuseIdentifier: String = "AddonCreatorCell.cell"
// MARK: - Private properties
private let viewDie = AddonGradientView()
private let labelTitle = UILabel() // createUILabel()
private let vStack = UIStackView() // createStack()
// MARK: - Initializator
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
vStack.axis = .vertical
vStack.spacing = 4
for v in [viewDie, labelTitle, vStack] {
v.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(v)
}
let g = contentView
// this will be updated in cellForItemAt
// it will keep the cells the full width of the collection view
wConstraint = viewDie.widthAnchor.constraint(equalToConstant: 300.0)
let bConstraint = vStack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0)
// giving these two constraints less-than-required priority avoids auto-layout complaints
wConstraint.priority = .required - 1
bConstraint.priority = .required - 1
NSLayoutConstraint.activate([
viewDie.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
viewDie.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
viewDie.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
viewDie.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
labelTitle.topAnchor.constraint(equalTo: g.topAnchor, constant: 16.0),
labelTitle.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelTitle.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
vStack.topAnchor.constraint(equalTo: labelTitle.bottomAnchor, constant: 8.0),
vStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
vStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
bConstraint,
wConstraint,
])
viewDie.colors = [
.init(red: 0.918, green: 0.990, blue: 0.325, alpha: 1.0),
.init(red: 0.824, green: 0.894, blue: 0.242, alpha: 1.0),
.init(red: 0.918, green: 0.990, blue: 0.325, alpha: 1.0),
]
viewDie.layer.cornerRadius = 16
}
// MARK: - Public methods
func fillData(model: CreateAddon) {
// if we have fewer "rows" than addons, create new "rows"
while vStack.arrangedSubviews.count < model.addons.count {
vStack.addArrangedSubview(TextFieldSwitch())
}
// configure "rows"
for (row, addon) in zip(vStack.arrangedSubviews, model.addons) {
if let v = row as? TextFieldSwitch {
v.fillData(d: addon)
}
}
// show used "rows" and hide unused "rows"
for (i, v) in vStack.arrangedSubviews.enumerated() {
v.isHidden = !(i < model.addons.count)
}
labelTitle.text = model.title
}
}
class AddonGradientView: UIView {
public var colors: [UIColor] = [.gray, .white] {
didSet {
gradientLayer.colors = colors.map { $0.cgColor }
}
}
override class var layerClass: AnyClass { CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
self.backgroundColor = .clear
gradientLayer.colors = colors.map { $0.cgColor }
}
}
看起来像这样:
注意:当用户在文本字段中键入或切换开关时,我没有实现任何数据更新......我将把它留给你。