使用Swift-5.0,Xcode-10.2,iOS-12.2和
简短的问题就在前面-如果您想深入研究问题,可以阅读下面的详细内容(..我试图为这个复杂的问题提供尽可能多的文献记录。)
简短问题:
您能否根据CollectionViewCell类中的目标动作来动态更改自定义UICollectionViewCell的高度(即,高度扩展)? (即,不在出队时通过相应UICollectionViewController委托方法的sizeForItemAt
方法-而是在自定义单元类本身内部)。
如果是,如何?????????
描述性很强的问题(观看视频和带有以下问题的详细描述...):
下面的视频说明了我的代码仍然存在有关可扩展UICollectionView单元的问题。
主要问题:为什么CollectionView-Cell仅在用户一直滚动到顶部之后才展开(而不是在??之前)?
据我了解(and posted in this answer),为了创建自动调整大小的CollectionView-Cell(即自动调整其高度),您必须实现两件事:
estimatedItemSize
属性的FlowLayout let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 1)
let vc = TestViewController(collectionViewLayout: collectionViewFlowLayout)
preferredLayoutAttributesFitting
方法(在我的示例中,提供用于跟踪单元格高度的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-Cell(黄色背景)才会扩展!
我敢肯定,只有滚动到顶部之后,preferredLayoutAttributesFitting
方法才会被再次调用。
问题是:如何引起preferredLayoutAttributesFitting
的呼叫(即不是滚动到顶部?)
或者是否有另一种方法可以使Cell调整其高度?
谢谢您的帮助。
这是我剩下的全部代码:
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-Cell看起来像这样:
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")
}
}
为了抽象出单元格组件的自动布局约束的锚定和高度定义(即,填充单元格的一堆标签和imageViews),我使用了以下UIView扩展...:
(扩展名已由Brian Voong发布-参见video link)
// 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
}
}