UILabel по центру UICollectionVIewCell с отступами по краям

Рейтинг: 0Ответов: 3Опубликовано: 06.05.2023

Как разместить UILabel внутри UICollectionViewCell по центру с отступами по бокам, как на картинке ниже? введите сюда описание изображения

Пытался делать так, но такой вариант не работает:

import UIKit
import SnapKit

class ViewController: UIViewController {

    @IBOutlet weak var myCollectionView: UICollectionView!

    var data: [String] = ["Здесь будет какой-то очень длинный текст", "Какой-нибудь текст", "Здесь будет какой-то очень длинный текст", "Ячейка среднего размера"]

    override func viewDidLoad() {
        super.viewDidLoad()

        myCollectionView.delegate = self
        myCollectionView.dataSource = self
    }
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.data.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = self.myCollectionView.dequeueReusableCell(withReusableIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell

        cell.layer.borderWidth = 1
        cell.layer.borderColor = UIColor.black.cgColor
        cell.layer.cornerRadius = 5

        cell.myLabel.text = self.data[indexPath.row]

        cell.myLabel.snp.makeConstraints { make in
            make.left.equalTo(cell.snp.left).offset(10)
            make.right.equalTo(cell.snp.right).offset(10)
        }

        return cell
    }
}

UPD: Добавил скриншот для наглядности того, что именно хочу получить в итоге.

введите сюда описание изображения

Попробовал посчитать количество букв в слове / фразе, чтобы автоматически задавать разную ширину для разных ячеек UICollectionViewCell, но, допустим, если слово из 4-х букв, то отступы слева и справа будут небольшими, а если фраза состоит, например, из 20-ти букв, то отступы по краям получаются очень существенными. Вот код:

import UIKit

class ViewController: UIViewController {

    var data: [String] = ["Все", "Одежда и обувь", "Товары для дома", "Электроника", "Мебель"]  //  Список тэгов
    var width: Int = 0  //  Ширина текущего тега

    @IBOutlet weak var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.delegate = self
        collectionView.dataSource = self
    }
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.data.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = .black

        /**
        *   Получаем количество букв в текущем слове (или фразе)
        */
        var len = self.data[indexPath.row].count
        /**
        *   Умножаем количество букв на 25 поинтов (т.к. оптимальная ширина на одну букву составляет 25 поинтов)
        */
        var width = ceil(len * 15)

        let label = UILabel(frame: CGRect(x: 0, y: 0, width: Int(width), height: 40))
        label.text = self.data[indexPath.row]
        label.backgroundColor = UIColor.red
        label.textColor = .black
        label.textAlignment = .center
        label.layoutMargins = UIEdgeInsets(top: 0, left: 80, bottom: 0, right: 10)

        cell.contentView.addSubview(label)

        return cell
    }
}

extension ViewController: UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        /**
        *   Получаем количество букв в текущем слове (или фразе)
        */
        var len = self.data[indexPath.row].count
        /**
        *   Умножаем количество букв на 25 поинтов (т.к. оптимальная ширина на одну букву составляет 25 поинтов)
        */
        var width = ceil(len * 15)

        return CGSize(width: Int(width), height: 40)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 10
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 10
    }
}

Что я упускаю из вида?

UPDATE: С помощью UIScrollView. Данный способ имелся ввиду?

import UIKit

class ViewController: UIViewController {

    let list: [String] = ["Первый", "Второй элемент", "Какой-нибудь новый элемент"]

    let stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = NSLayoutConstraint.Axis.horizontal
        stackView.distribution = UIStackView.Distribution.equalSpacing
        stackView.spacing = 16.0
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()

    let scrollView: UIScrollView = {
        let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 80))
        scrollView.backgroundColor = .red
        scrollView.contentSize = CGSize(width: 1000, height: 200)
        return scrollView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(scrollView)

        DispatchQueue.main.async {
            self.list.forEach( { $0
                let label = self.createLabel(text: $0)
                label.frame = CGRect(x: label.frame.origin.x, label.frame.origin.y, width: label.frame.size.width, height: 40)
                self.stackView.addArrangedSubview(label)
            })
        }

        scrollView.addSubview(stackView)

        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 80),
            stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 10),
            stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -10)
        ])
    }

    private func createLabel(text: String) -> UILabel {
        let label = UILabel()
        label.font = UIFont(name: "Halvetica", size: 17)
        label.numberOfLines = 1
        label.text = text
        label.sizeToFit()
        label.backgroundColor = .white
        label.frame = CGRect(x: 0, y: 0, width: label.frame.width, height: 40)

        return label
    }
}

Ответы

▲ 1Принят
class ViewController: UIViewController {

    @IBOutlet weak var myCollectionView: UICollectionView!

    var data: [String] = ["Здесь будет какой-то очень длинный текст", "Какой-нибудь текст", "Здесь будет какой-то очень длинный текст", "Ячейка среднего размера"]

    override func viewDidLoad() {
        super.viewDidLoad()

        myCollectionView.delegate = self
        myCollectionView.dataSource = self
        
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        myCollectionView.collectionViewLayout = layout
        myCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        data.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = myCollectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CustomCollectionViewCell
        let text = data[indexPath.row]
        cell.updateText(text: text)
        return cell
    }
}

extension ViewController: UICollectionViewDelegateFlowLayout {
    
    // рассчитывает размер ячейки коллекции
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // расчитываем сколько места (ширины) занимает текст с заданным шрифтом
        let text = data[indexPath.row]
        let size = CGSize(width: .greatestFiniteMagnitude, height: 100.0) // высоту берем как высота колекции
        let font = UIFont.systemFont(ofSize: 20) // нужно брать фонт лейбла в ячейке
        let rect = (text as NSString).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
        let result = CGSize(width: rect.size.width, height: 100)
        return result
    }
}


class CustomCollectionViewCell: UICollectionViewCell {
    
    private var label: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.layer.borderWidth = 1
        contentView.layer.borderColor = UIColor.black.cgColor
        contentView.layer.cornerRadius = 5
        contentView.layer.masksToBounds = true
        
        contentView.backgroundColor = .yellow
        
        label = UILabel()
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(label)
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func updateText(text: String) {
        label.text = text
    }
    
}

введите сюда описание изображения

▲ 1

Более продвинутое и как мне кажется менее геморойное решение (iOS 13+)

private func createLayout() -> UICollectionViewLayout {
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .estimated(100.0),
        heightDimension: .fractionalHeight(1.0))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    let groupSize = NSCollectionLayoutSize(
        widthDimension: .estimated(100.0),
        heightDimension: .absolute(44))
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
    let section = NSCollectionLayoutSection(group: group)
    section.orthogonalScrollingBehavior = .continuous
    section.interGroupSpacing = 20
    let layout = UICollectionViewCompositionalLayout(section: section)
    return layout
}

Надо может поиграться с параметрами айтема и группы, но в целом подход такой что мы ставим расчет ширины автоматически (.estimated), тут еще важно чтобы констрейнты в ячейке были верно расставлены.

И в viewDidLoad присваиваем новый лайаут.

...
myCollectionView.collectionViewLayout = createLayout()
▲ 0
  1. Вы выровнили текст по горизонтали, но не по вертикали. Нужно добавить центрирование, то есть добавить недостающие контрейты для вертикали. Что-то вроде этого

make.centerY.equalTo(superview)

  1. установка контрейнтов для ячейки в cellForItemAt неправильный подход, они должны быть в ксибе/стироборде или там где ячейка создается.