繁体   English   中英

尝试通过使用 UICollectionViewCompositionalLayout 实现固定高度、动态宽度(包裹内容)水平 UICollectionView 的单元格

[英]Attempt to achieve fixed height, dynamic width (wrap content) horizontal UICollectionView's cell by using UICollectionViewCompositionalLayout

https://stackoverflow.com/a/51231881/72437 中,它展示了如何通过使用UICollectionViewCompositionalLayout在垂直UICollectionView的单元格中实现全宽、动态高度

我们希望在水平UICollectionView上实现相同的要求

  1. 固定高度 44
  2. 最小宽度 44
  3. 当内容增长时,宽度应动态放大

这是我们的解决方案的样子

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

我们期望通过使用widthDimension: NSCollectionLayoutDimension.estimated(44) ,这是使单元格宽度动态增长的关键。 但是,这并不像预期的那样工作。 看起来像

在此处输入图片说明

我可以知道,我们如何通过使用UICollectionViewCompositionalLayout来解决这个问题? 完整可行的项目位于https://github.com/yccheok/PageViewControllerWithTabs/tree/UICollectionViewCompositionalLayout


每秒

我们想避免使用UICollectionViewDelegateFlowLayout method collectionView(_:layout:sizeForItemAt:) 因为,我们的单元格可以变得复杂,它会包含除UILabel之外的其他视图。 必须手动计算内容大小会使解决方案不灵活且容易出错。

我已经创造了你需要的东西。 请按照以下步骤操作:

  • 创建一个内部带有标签的单元格。
  • 向标签添加尾随和前导约束(我的示例有 10 和 10)(重要)
  • 将标签设置为垂直对齐。
  • 将标签的宽度设置为 >= 44。您可以通过检查约束并从等于 >= 更改来做到这一点。

完成此操作后,您需要创建布局。 我有以下功能(在你的情况下,我的身高 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
    }
}

然后在您的 viewDidLoad() 函数中,在您调用委托或 dataSource 方法之前,您需要调用以下内容(假设您保持相同的函数名称):

collectionView.collectionViewLayout = layoutConfig()

你应该得到以下结果。 它水平滚动: 在此处输入图片说明

这是示例的源代码:

https://github.com/williamfinn/Horizo​​ntalCollectionExample

你似乎一遍又一遍地问同样的问题?

如果您先提出完整的问题,您可能会发现它很有帮助。

您说“我们的单元格内容可以变得复杂”,但您没有提供有关“复杂”可能是什么的任何信息。

下面是一个可能让您朝着正确方向前进的示例。

首先,输出......每个“标签”都有一个图像视图、一个标签和一个按钮,或者是如下元素的组合:

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

使用不同颜色的标签:

在此处输入图片说明

在此处输入图片说明

使用相同颜色的选项卡,除了“活动”选项卡:

在此处输入图片说明

在此处输入图片说明

在选项卡元素上使用背景颜色查看框架:

在此处输入图片说明

在此处输入图片说明

我用它作为标签信息的结构:

struct TabInfo {
    var name: String? = ""
    var color: Int = 0
    var imageName: String? = ""
    var buttonTitle: String? = ""
}

我从你的 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)
    }
}

视图控制器的示例代码:

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

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
    }

}

最后,集合视图单元格:

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

请注意,这只是示例代码!!!

它不是为生产就绪而准备的 --- 只是为了帮助您。

实现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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM