将 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()
但这让我单元格向左对齐并按照我需要的方式换行,但单元格宽度不符合我的约束或我的数据,它看起来像这样。
使用此布局类进行标签布局
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
}
}
您可以使用 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)
...
}
结果
如果您的单元格受到适当的约束,您应该能够实现您正在寻找的布局。如果您还有其他问题,请在评论中告诉我。