[英]Attempt to achieve fixed height, dynamic width (wrap content) horizontal UICollectionView's cell by using UICollectionViewCompositionalLayout
In https://stackoverflow.com/a/51231881/72437 , it show how to achieve a full width, dynamic height in vertical UICollectionView
's cell, by using UICollectionViewCompositionalLayout
在https://stackoverflow.com/a/51231881/72437 中,它展示了如何通过使用
UICollectionViewCompositionalLayout
在垂直UICollectionView
的单元格中实现全宽、动态高度
We would like to achieve the same on a horizontal UICollectionView
, with requirements我们希望在水平
UICollectionView
上实现相同的要求
Here's how our solution looks like这是我们的解决方案的样子
class MenuTabsView: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
lazy var collView: UICollectionView = {
let itemSize = NSCollectionLayoutSize(
widthDimension: NSCollectionLayoutDimension.estimated(44),
heightDimension: NSCollectionLayoutDimension.fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(
layoutSize: itemSize
)
item.contentInsets = NSDirectionalEdgeInsets(
top: 0,
leading: 0,
bottom: 0,
trailing: 1
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: itemSize,
subitem: item,
count: 1
)
let section = NSCollectionLayoutSection(group: group)
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)
let cv = UICollectionView.init(frame: CGRect.zero, collectionViewLayout: layout)
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = self
return cv
}()
We expect by using widthDimension: NSCollectionLayoutDimension.estimated(44)
, that's the key to make the cell width grow dynamically.我们期望通过使用
widthDimension: NSCollectionLayoutDimension.estimated(44)
,这是使单元格宽度动态增长的关键。 However, that doesn't work as expected.但是,这并不像预期的那样工作。 It looks like
看起来像
May I know, how can we solve this problem by using UICollectionViewCompositionalLayout
?我可以知道,我们如何通过使用
UICollectionViewCompositionalLayout
来解决这个问题? The complete workable project is located at https://github.com/yccheok/PageViewControllerWithTabs/tree/UICollectionViewCompositionalLayout完整可行的项目位于https://github.com/yccheok/PageViewControllerWithTabs/tree/UICollectionViewCompositionalLayout
p/s每秒
We want to avoid from using UICollectionViewDelegateFlowLayout method collectionView(_:layout:sizeForItemAt:)
.我们想避免使用
UICollectionViewDelegateFlowLayout method collectionView(_:layout:sizeForItemAt:)
。 As, our cell can grow complex, and it will contain other views besides UILabel
.因为,我们的单元格可以变得复杂,它会包含除
UILabel
之外的其他视图。 Having to calculate the content size manually, will make the solution inflexible and error prone.必须手动计算内容大小会使解决方案不灵活且容易出错。
I have created what you need.我已经创造了你需要的东西。 Follow the following steps:
请按照以下步骤操作:
Once this is done you need to create your layout.完成此操作后,您需要创建布局。 I had the following function (I have height 50 just change to 44 in your case):
我有以下功能(在你的情况下,我的身高 50 只是更改为 44):
func layoutConfig() -> UICollectionViewCompositionalLayout {
return UICollectionViewCompositionalLayout { (sectionNumber, env) -> NSCollectionLayoutSection? in
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(44), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(44), heightDimension: .absolute(50))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
return section
}
}
Then in your viewDidLoad() function before you call the delegate or dataSource methods you need to call the following (assuming you keep the same function name):然后在您的 viewDidLoad() 函数中,在您调用委托或 dataSource 方法之前,您需要调用以下内容(假设您保持相同的函数名称):
collectionView.collectionViewLayout = layoutConfig()
You should end up with the following.你应该得到以下结果。 It scrolls horizontally:
它水平滚动:
Here is the source code to the example:这是示例的源代码:
https://github.com/williamfinn/HorizontalCollectionExample https://github.com/williamfinn/HorizontalCollectionExample
You seem to be asking the same question over and over?你似乎一遍又一遍地问同样的问题?
You may find it helpful if you ask your complete question first.如果您先提出完整的问题,您可能会发现它很有帮助。
You say "our cell content can grow complex" but you don't provide any information about what "complex" might be.您说“我们的单元格内容可以变得复杂”,但您没有提供有关“复杂”可能是什么的任何信息。
Here's an example that might get you headed in the right direction.下面是一个可能让您朝着正确方向前进的示例。
First, the output... Each "tab" has an Image View, a Label and a Button, or a combination of elements as follows:首先,输出......每个“标签”都有一个图像视图、一个标签和一个按钮,或者是如下元素的组合:
// 1st tab is Image + Label + Button
// 2nd tab is Label + Button
// 3rd tab is Image + Label
// 4th tab is Image + Button
// 5th tab is Label Only
// 6th tab is Button Only
// 7th tab is Image Only
With different color tabs:使用不同颜色的标签:
With the same color tabs, except for the "active" tab:使用相同颜色的选项卡,除了“活动”选项卡:
With background colors on the tab elements to see the frames:在选项卡元素上使用背景颜色查看框架:
I used this as the struct for the tab info:我用它作为标签信息的结构:
struct TabInfo {
var name: String? = ""
var color: Int = 0
var imageName: String? = ""
var buttonTitle: String? = ""
}
and I used this from your GitHub repo:我从你的 GitHub 存储库中使用了这个:
class Utils {
static func intToUIColor(argbValue: Int) -> UIColor {
// & binary AND operator to zero out other color values
// >> bitwise right shift operator
// Divide by 0xFF because UIColor takes CGFloats between 0.0 and 1.0
let red = CGFloat((argbValue & 0xFF0000) >> 16) / 0xFF
let green = CGFloat((argbValue & 0x00FF00) >> 8) / 0xFF
let blue = CGFloat(argbValue & 0x0000FF) / 0xFF
let alpha = CGFloat((argbValue & 0xFF000000) >> 24) / 0xFF
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}
Sample code for the view controller:视图控制器的示例代码:
class TabsTestViewController: UIViewController {
// 1st tab is Image + Label + Button
// 2nd tab is Label + Button
// 3rd tab is Image + Label
// 4th tab is Image + Button
// 5th tab is Label Only
// 6th tab is Button Only
// 7th tab is Image Only
var tabs = [
TabInfo(name: "All", color: 0xff5481e6, imageName: "swiftBlue64x64", buttonTitle: "One"),
TabInfo(name: "Calendar", color: 0xff7cb342, imageName: nil, buttonTitle: "Two"),
TabInfo(name: "Home", color: 0xffe53935, imageName: "swiftBlue64x64", buttonTitle: nil),
TabInfo(name: nil, color: 0xfffb8c00, imageName: "swiftBlue64x64", buttonTitle: "Work"),
TabInfo(name: "Label Only", color: 0xffe00000, imageName: nil, buttonTitle: nil),
TabInfo(name: nil, color: 0xff008000, imageName: nil, buttonTitle: "Button Only"),
TabInfo(name: nil, color: 0xff000080, imageName: "swiftBlue64x64", buttonTitle: nil),
]
let menuTabsView: MenuTabsView = {
let v = MenuTabsView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let otherView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(menuTabsView)
view.addSubview(otherView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain menuTabsView Top / Leading / Trailing
menuTabsView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
menuTabsView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
menuTabsView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
// constrain otherView Leading / Trailing / Bottom
otherView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
otherView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
otherView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
// constrain otherView Top to menuTabsView Bottom
otherView.topAnchor.constraint(equalTo: menuTabsView.bottomAnchor, constant: 0.0),
])
// un-comment to set all tab colors to green - 0xff7cb342
// except first tab
//for i in 1..<tabs.count {
// tabs[i].color = 0xff7cb342
//}
menuTabsView.dataArray = tabs
// set background color of "bottom bar" to first tab's background color
guard let tab = tabs.first else {
return
}
menuTabsView.bottomBar.backgroundColor = Utils.intToUIColor(argbValue: tab.color)
}
}
Sample code for the MenuTabsView
: MenuTabsView
示例代码:
class MenuTabsView: UIView {
var tabsHeight: CGFloat = 44
var dataArray: [TabInfo] = [] {
didSet{
self.collView.reloadData()
}
}
lazy var collView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 1
// needed to prevent last cell being clipped
layout.minimumInteritemSpacing = 1
layout.estimatedItemSize = CGSize(width: 100, height: self.tabsHeight)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
return cv
}()
let bottomBar: UIView = {
let v = UIView()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
collView.translatesAutoresizingMaskIntoConstraints = false
bottomBar.translatesAutoresizingMaskIntoConstraints = false
addSubview(collView)
addSubview(bottomBar)
NSLayoutConstraint.activate([
// collection view constrained Top / Leading / Trailing
collView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
collView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
collView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// collection view Height constrained to 44
collView.heightAnchor.constraint(equalToConstant: tabsHeight),
// "bottom bar" constrained Leading / Trailing / Bottom
bottomBar.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
bottomBar.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
bottomBar.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
// "bottom bar" Height constrained to 4-pts
bottomBar.heightAnchor.constraint(equalToConstant: 4.0),
// collection view Bottom constrained to "bottom bar" Top
collView.bottomAnchor.constraint(equalTo: bottomBar.topAnchor),
])
collView.register(MyStackCell.self, forCellWithReuseIdentifier: "cell")
collView.dataSource = self
collView.delegate = self
collView.backgroundColor = .clear
backgroundColor = .white
}
}
extension MenuTabsView: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyStackCell
let t = dataArray[indexPath.item]
cell.configure(with: t.name, imageName: t.imageName, buttonTitle: t.buttonTitle, bkgColor: t.color)
//cell.configure(with: t.name, or: nil, or: "My Button")
return cell
}
}
and finally, the collection view cell:最后,集合视图单元格:
class MyStackCell: UICollectionViewCell {
// can be set by caller to change default cell height
public var stackHeight: CGFloat = 36.0
private var stackHeightConstraint: NSLayoutConstraint!
private let label: UILabel = {
let v = UILabel()
v.textAlignment = .center
return v
}()
private let imageView: UIImageView = {
let v = UIImageView()
return v
}()
private let button: UIButton = {
let v = UIButton()
v.setTitleColor(.white, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
return v
}()
private let stack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.alignment = .center
v.spacing = 8
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(stack)
// stack views in cells get cranky
// so we set priorities on desired element constraints to 999 (1 less than required)
// to avoid console warnings
var c = imageView.heightAnchor.constraint(equalToConstant: 32.0)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// image view has 1:1 ratio
c = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// minimum width for label if desired
c = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// minimum width for button if desired
c = button.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// height for stack view
stackHeightConstraint = stack.heightAnchor.constraint(equalToConstant: stackHeight)
stackHeightConstraint.priority = UILayoutPriority(rawValue: 999)
stackHeightConstraint.isActive = true
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0),
stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0),
])
stack.addArrangedSubview(imageView)
stack.addArrangedSubview(label)
stack.addArrangedSubview(button)
// during development, so we can see the frames
// delete or comment-out when satisfied with layout
//imageView.backgroundColor = .yellow
//label.backgroundColor = .green
//button.backgroundColor = .blue
}
func customTabHeight(_ h: CGFloat) -> Void {
stackHeightConstraint.constant = h
}
func configure(with name: String?, imageName: String?, buttonTitle: String?, bkgColor: Int?) -> Void {
// set and show elements
// or hide if nil
if let s = imageName, s.count > 0 {
if let img = UIImage(named: s) {
imageView.image = img
imageView.isHidden = false
}
} else {
imageView.isHidden = true
}
if let s = name, s.count > 0 {
label.text = s
label.isHidden = false
} else {
label.isHidden = true
}
if let s = buttonTitle, s.count > 0 {
button.setTitle(s, for: [])
button.isHidden = false
} else {
button.isHidden = true
}
if let c = bkgColor {
backgroundColor = Utils.intToUIColor(argbValue: c)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// set the mask in layoutSubviews
let maskPath = UIBezierPath(roundedRect: bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 12.0, height: 12.0))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
}
}
Note that this is Example Code Only!!!请注意,这只是示例代码!!!
It is not intended to be production-ready --- it's just to help you on your way.它不是为生产就绪而准备的 --- 只是为了帮助您。
Implement UICollectionViewDelegateFlowLayout
method collectionView(_:layout:sizeForItemAt:)
method to return the cell size according to your text.实现
UICollectionViewDelegateFlowLayout
方法collectionView(_:layout:sizeForItemAt:)
方法以根据您的文本返回单元格大小。
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = "This is your text"
let size = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:18.0)])
return CGSize(width: size.width + 10.0, height: 44.0)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.