簡體   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