如何在 Swift iOS 中以等间距和动态宽度将 CollectionView 单元格向左对齐?

问题描述 投票:0回答:2

将 CollectionView 单元格向左对齐,间距相等(单元格将具有动态宽度,具体取决于接收到的数据,即单元格中的字符串长度)。The Blue 1 is my first query and the yellow 2 is my other query.

我的自定义视图是TrendingView.swift

//
//  TrendingView.swift

//
//  Created by Abhishek Chandrakant Gidde on 24/05/23.

//

import UIKit

protocol TrendingViewDelegate: AnyObject {
    func didSelectScrip(scrip:ScripModel)
    func seeAllScrips()
    func seeAllBreakouts()
}

enum DataType {
    case EQUITY
    case DERIVATIVES
    
    func isEquityData() -> Bool {
        switch self {
        case .EQUITY:
            return true
        case .DERIVATIVES:
            return false
        }
    }
}

class TrendingView: UIView {
    
    @IBOutlet var contentViewRoot: UIView!
    @IBOutlet weak var lblTrendingTitle: UILabel!
    @IBOutlet weak var clvTrendingData: UICollectionView!
    
    var isTrending = true
    
    var dataType: DataType?
    
    var arrayTrendingData = [MarketScripModel]() {
        didSet{
            self.updateCollectionViewUI()
        }
    }
    
    var arrayBreakoutData = [SignalListModel]() {
        didSet{
            self.updateCollectionViewUI()
        }
    }
    
    // MARK: Properties
    
    weak var delegate:TrendingViewDelegate?
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        customInit()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        customInit()
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        customInit()
    }
    
    func customInit(){
        Bundle.main.loadNibNamed("TrendingView",owner: self, options: nil)
        addSubview(self.contentViewRoot)
        

        self.contentViewRoot.frame = self.bounds
        self.contentViewRoot.autoresizingMask = [.flexibleWidth,.flexibleHeight]
        clvTrendingData.dataSource = self
        clvTrendingData.delegate = self
        clvTrendingData.register(UINib(nibName: "TrendingViewCell", bundle: .main),forCellWithReuseIdentifier: "trendingViewCell")
        clvTrendingData.collectionViewLayout = CustomCollectionViewFlowLayout()
    
        
        
    }
    
    func updateCollectionViewUI() {
        DispatchQueue.main.async {
            if self.isTrending {
                if self.dataType == .EQUITY {
                    self.lblTrendingTitle.text = "Trending Stocks"
                } else if self.dataType == .DERIVATIVES {
                    self.lblTrendingTitle.text = "Trending Strikes"
                }
            } else {
                if self.dataType == .EQUITY {
                    self.lblTrendingTitle.text = "Breakout Stocks"
                } else if self.dataType == .DERIVATIVES {
                    self.lblTrendingTitle.text = "Breakout Futures"
                }
            }
            //
            self.clvTrendingData.reloadData()
        }
    }
    
    // MARK: Actions
    
    @IBAction func onSellAllTap(_ sender: Any) {
        if(isTrending){
            self.delegate?.seeAllScrips()
            
        }
        else{
            self.delegate?.seeAllBreakouts()
        }
    }
}

// CollectionView FlowLayout
extension TrendingView:UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    }

}

extension TrendingView: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if self.isTrending {
            return self.arrayTrendingData.count
        } else {
            return self.arrayBreakoutData.count
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let trendingViewCell: TrendingViewCell = clvTrendingData.dequeueReusableCell(withReuseIdentifier: "trendingViewCell", for: indexPath) as! TrendingViewCell
        
        if(isTrending) {
            print("Returning Trending Data Cell")
            trendingViewCell.configureCell(scripModel: arrayTrendingData[indexPath.row],isEquityData: dataType!.isEquityData() )
        } else {
            print("Returning Derivative Data Cell")
            trendingViewCell.configureCell(scripModel: arrayBreakoutData[indexPath.row],isEquityData: dataType!.isEquityData())
        }
        
        trendingViewCell.layoutIfNeeded()
        return trendingViewCell
    }
}

extension TrendingView: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if(isTrending){
            self.delegate?.didSelectScrip(scrip: arrayTrendingData[indexPath.row].scrip)}
        else{
            self.delegate?.didSelectScrip(scrip: arrayBreakoutData[indexPath.row].scrip)
        }
    }
}




我的趋势视图单元格是TrendingViewCell.swift

//
//  TrendingViewCell.swift
//
//  Created by Abhishek Chandrakant Gidde on 24/05/23.
//

import UIKit

enum CallType: String {
    case BUY = "buy"
    case SELL = "sell"
}


class TrendingViewCell: UICollectionViewCell {
    
    @IBOutlet weak var lblScripTitle: UILabel!
    @IBOutlet weak var ivTrendingIcon: UIImageView!
    
    private var isStockPositive: Bool?{
        didSet{
            updateUI()
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    

    func configureCell(scripModel: MarketScripModel, isEquityData:Bool){
        
        if(!isEquityData){
            
            let nameOfScrip = scripModel.marketScripModel.Name
            
            let components = nameOfScrip?.components(separatedBy: "|")
            
            // Extracting the underlying asset (e.g., "NIFTY")
            let underlyingAsset = components?[0].trimmingCharacters(in: .whitespaces)
            
            // Extracting the expiration date (e.g., "25MAY23")
            let expirationDate = components?[1].trimmingCharacters(in: .whitespaces)
            
            let strikePrice = scripModel.marketScripModel.LTP
            
            let trendingStrikeText = "\(underlyingAsset!) \(strikePrice!) \(expirationDate!)"
            print("debugabhi:")
            print(underlyingAsset!)
            print(expirationDate!)
            print(strikePrice!)
            print(trendingStrikeText)
            
            self.lblScripTitle.text=trendingStrikeText
//            lblScripTitle.text = "NIFTY 18,400 25MAY2023"
            
        }
        else{
            lblScripTitle.text = scripModel.scrip.scripNameWithExpiry()}
           
        
        if let ltpChange = scripModel.marketScripModel.PerChange {
            isStockPositive = ltpChange >= 0
        }
        
    }
    
    func configureCell(scripModel: SignalListModel, isEquityData:Bool){
        
        if(!isEquityData){
            
            let nameOfScrip = scripModel.scrip.scripNameWithExpiry()
            
            self.lblScripTitle.text = nameOfScrip
            if scripModel.callType.removingWhitespaces().lowercased() == CallType.BUY.rawValue  {
                isStockPositive =  true
            }
            else {
                isStockPositive = false
            }

            
        }
        else{
            lblScripTitle.text = scripModel.scrip.scripNameWithExpiry()
            
            
            if scripModel.scrip.changePer() >= 0{
                isStockPositive =  true
            }
            else{
                isStockPositive = false
            }
        }
           
       
        
    }
    
    func updateUI(){
        self.ivTrendingIcon.image = isStockPositive! ? UIImage(named: "icn_stockPositive") : UIImage(named: "icn_stockNegative")

    }
    

}

这是需要的设计 THIS IS THE UI DESIGN REQUIRED

我已尽力尽我所能,但无法按照需要的方式获得它太令人沮丧了,我还尝试使用 ChatGPT 建议的自定义类,如

class WrapFlowLayout: UICollectionViewFlowLayout {...
和设置
collectionView.collectionViewLayout = WrapflowLayout()
但这让我单元格向左对齐并按照我需要的方式换行,但单元格宽度不符合我的约束或我的数据,它看起来像这样。 enter image description here

ios swift uicollectionview uicollectionviewcell xcode-storyboard
2个回答
1
投票

使用此布局类进行标签布局

import UIKit

class TagFlowLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let attributes = super.layoutAttributesForElements(in: rect) else {
            return nil
        }

        var rows = [Row]()
        var currentRowY: CGFloat = -1

        for attribute in attributes {
            if currentRowY != attribute.frame.origin.y {
                currentRowY = attribute.frame.origin.y
                rows.append(Row(spacing: 10))
            }
            rows.last?.add(attribute: attribute)
        }

        rows.forEach {
            $0.tagLayout(collectionViewWidth: collectionView?.frame.width ?? 0)
        }
        return rows.flatMap { $0.attributes }
    }
}


class Row {
    var attributes = [UICollectionViewLayoutAttributes]()
    var spacing: CGFloat = 0

    init(spacing: CGFloat) {
        self.spacing = spacing
    }

    func add(attribute: UICollectionViewLayoutAttributes) {
        attributes.append(attribute)
    }

    func tagLayout(collectionViewWidth: CGFloat) {
        let padding = 10
        var offset = padding
        for attribute in attributes {
            attribute.frame.origin.x = CGFloat(offset)
            offset += Int(attribute.frame.width + spacing)
        }
    }
}

之后给出单元格的估计大小

class ViewController {

 override func viewDidLoad() {
        super.viewDidLoad()
   
        let layout = TagFlowLayout()
        layout.estimatedItemSize = CGSize(width: 140, height: 40)
        collectionView.collectionViewLayout = layout
    }



    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell",
                                                            for: indexPath) as? TagCollectionViewCell else {
            return MyCell()
        }
        cell.tagLabel.text = titles[indexPath.section][indexPath.row]
        cell.tagLabel.preferredMaxLayoutWidth = collectionView.frame.width // if there is nothing next to label

       cell.tagLabel.preferredMaxLayoutWidth = collectionView.frame.width - 16 // if there is button or icon next to label with size of 16 or whatever
        
        return cell
    }

}

0
投票

您可以使用 Composure - 我编写的一个开源库,可以相当轻松地实现此目的。无需创建您自己的自定义类或流程布局。看起来您的视图中有 2 个部分,并且这两个部分都需要以相同的方式布局。您的单元格似乎具有固定高度但动态宽度。换句话说,两个部分中的细胞都需要根据内容横向生长。

第 1 步:将 Composure 添加到项目后,定义一个枚举,如下所示:

import UIKit
import Composure

enum MyCollectionViewLayout: Int, CaseIterable, DefinesCompositionalLayout {
    case trendingStrikesSection
    case breakoutFuturesSection
    
    func layoutInfo(using layoutEnvironment: NSCollectionLayoutEnvironment) -> CompositionalLayoutOption {
        switch self {
        //try to choose a realistic estimate for your width. Don't worry, your cells will grow or shrink even when your estimate is off. 
        case .trendingStrikesSection:
            return .dynamicWidthFixedHeight(estimatedWidth: 120, fixedHeight: 75)
        case .breakoutFuturesSection:
            return .dynamicWidthFixedHeight(estimatedWidth: 150, fixedHeight: 150)
        }
    }

    // choose a height that is appropriate for your header view
    func headerInfo(using layoutEnvironment: NSCollectionLayoutEnvironment) -> CompositionalLayoutOption? {
        switch self {
        case .trendingStrikesSection:
            return .fullWidthFixedHeight(fixedHeight: 45)
        case .breakoutFuturesSection:
            return .fullWidthFixedHeight(fixedHeight: 45)
        }
    }

    // Optional: determines the space between two cells
    var interItemSpacing: CGFloat {
        switch self {
        case .trendingStrikesSection:
            return 10
        case .fullWidthFixedHeight:
            return 15
        }
    }

    // determines the space between two rows of cells
    var interGroupSpacing: CGFloat {
        return 20
    }
}

第 2 步: 在视图控制器中配置集合视图时,请将此行用于您的 collectionViewLayout,而不是当前拥有的任何内容。

....
import Composure //don't forget to add Composure to your view controller
...

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    //this replaces your existing layout code
    collectionView.collectionViewLayout = generateComposionalLayout(with: MyCollectionViewLayout.allCases)
    ...
}

结果

如果您的单元格受到适当的约束,您应该能够实现您正在寻找的布局。如果您还有其他问题,请在评论中告诉我。

an example collection view with self-sizing cells

© www.soinside.com 2019 - 2024. All rights reserved.