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

wkyowqbh  于 2023-06-04  发布在  Swift
关注(0)|答案(2)|浏览(243)

将CollectionView单元格向左对齐,单元格之间的间距相等(单元格的宽度取决于接收到的数据,即单元格中字符串的长度)。

我的自定义视图是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")

    }
    

}

这是所需的设计

我已经尽我所能做的一切,但它是太令人沮丧,让它的方式是需要的,我也尝试使用自定义类的建议,由ChatGPT像class WrapFlowLayout: UICollectionViewFlowLayout {...和设置collectionView.collectionViewLayout = WrapflowLayout(),但这让我与细胞对齐向左和 Package ,正如我需要的,但然后细胞宽度不是根据我的约束或我的数据,它看起来像这样。

b1uwtaje

b1uwtaje1#

将此布局类用于标记布局

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
    }

}
wlp8pajw

wlp8pajw2#

您可以使用Composure-一个第三方开源库来相当容易地实现这一点。无需创建自己的自定义类或流布局。它看起来像你有两个部分在你的看法,但这两个部分需要以相同的方式布局。您的单元格看起来具有固定的高度和动态的宽度。换句话说,这两个部分中的单元格都需要根据内容在宽度上增长。

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

    // Optional: Only needed if you have header or footer views for each section
    func headerInfo(using layoutEnvironment: NSCollectionLayoutEnvironment) -> CompositionalLayoutOption? {
        switch self {
        case .trendingStrikesSection:
            //recommended layout for headers and footers is fullWidthFixedHeight
            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
        }
    }

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

**第二步:**在View Controller中配置collection view时,将该行用于collectionViewLayout,而不是当前的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)
    ...
}

结果

如果单元格被正确地约束,则应该能够实现所需的布局。如果您有其他问题,请在评论中告诉我们。

相关问题