How to make horizontal CollectionView size fit its content programatically - ios

I have a collection view which takes up the whole screen and should scroll horizontally.
Problem is I can't adjust the size for item while retaining the carousel effect. If I lower the size for item, the cell stacks vertically just like here:
The pink background is the collectionView. The collectionView fills the viewController's view that is why the cells doesn't align horizontally.
The alignment should be like this which scrolls horizontally much like a carousel but the problem is it's too big and it took up the whole view.
What's expected is for the cell and collection view to be around 100 height or 200 height only and can still scroll horizontally.
This is my existing code:
I am using SnapKit for constraints.
ViewController
import UIKit
import SnapKit
class ViewController: UIViewController {
let viewModel: [SeatViewModel] = [.init(text: "single text"), .init(text: "slightly longer text."), .init(text: "very very very very long long text")]
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.backgroundColor = .white
cv.register(SeatCardCell.self, forCellWithReuseIdentifier: "SeatCardCell")
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(200)
make.height.equalTo(100)
}
collectionView.dataSource = self
collectionView.backgroundColor = .systemPink
}
func lines(label: UILabel) -> Int {
self.collectionView.layoutIfNeeded()
let textSize = CGSize(width: label.frame.size.width, height: CGFloat(Float.infinity))
let rHeight = lroundf(Float(label.sizeThatFits(textSize).height))
let charSize = lroundf(Float(label.font.lineHeight))
let lineCount = rHeight/charSize
return lineCount
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectioCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeatCardCell", for: indexPath) as? SeatCardCell else { fatalError() }
collectioCell.backgroundColor = [UIColor.green, .red, .black, .brown, .yellow].randomElement()
collectioCell.viewModel = viewModel[indexPath.row]
return collectioCell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 200, height: self.collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
}
View Model and Cell
import UIKit
import SnapKit
struct SeatViewModel {
let text: String
}
class SeatCardCell: UICollectionViewCell {
var viewModel: SeatViewModel? {
didSet {
configure()
}
}
// MARK: - Properties
let seatImageView = UIImageView(image: nil)
let seatLabel: CustomLabel = {
let label = CustomLabel()
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
guard let viewModel = viewModel else { return }
seatLabel.text = viewModel.text
seatLabel.backgroundColor = .cyan
seatLabel.sizeToFit()
seatImageView.image = .strokedCheckmark
let stackView = UIStackView(arrangedSubviews: [seatImageView, seatLabel])
stackView.spacing = 0
stackView.alignment = .center
addSubview(stackView)
seatImageView.snp.makeConstraints { make in
make.size.equalTo(40)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
// MARK: - CUSTOM TITLE LABEL
class CustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.textColor = .darkGray
self.textAlignment = .left
self.numberOfLines = 4
}
}

The problem of your code is you set again the collection view frame equal to view frame in viewDidLayoutSubviews.
You just need to remove it and your code will work perfectly fine.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// remove it
//collectionView.frame = view.bounds
}

Related

How to make collection view cell height equal to the text with the highest height and programatically

I have a collection view which scrolls horizontally much like a carousel.
The height and width of all the cells should be equal to the cell which has the longest text.
It doesn't do this however and instead expands it's width depending on the length of the text which results into different widths of cells though the height remains constant.
max number of lines for the text is only 4.
I tried other answers in SO but none of them worked for me.
Here is my code, I am using SnapKit for constraints.
View Controller
import UIKit
import SnapKit
class ViewController: UIViewController {
let viewModel: [SeatViewModel] = [.init(text: "single text"), .init(text: "slightly longer text."), .init(text: "very very very very long long text")]
var collectionViewSize: CGFloat = 10
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = CGSize(width: 200, height: 100)
let cv = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
cv.register(SeatCardCell.self, forCellWithReuseIdentifier: "SeatCardCell")
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.height.equalTo(100)
}
collectionView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectioCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeatCardCell", for: indexPath) as? SeatCardCell else { fatalError() }
collectioCell.backgroundColor = [UIColor.green, .red, .black, .brown, .yellow].randomElement()
collectioCell.viewModel = viewModel[indexPath.row]
return collectioCell
}
}
Cell and View Model
struct SeatViewModel {
let text: String
}
class SeatCardCell: UICollectionViewCell {
var viewModel: SeatViewModel? {
didSet {
configure()
}
}
// MARK: - Properties
let seatImageView = UIImageView(image: nil)
let seatLabel: CustomLabel = {
let label = CustomLabel()
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
guard let viewModel = viewModel else { return }
seatLabel.text = viewModel.text
seatLabel.backgroundColor = .cyan
seatLabel.sizeToFit()
seatImageView.image = .strokedCheckmark
let stackView = UIStackView(arrangedSubviews: [seatImageView, seatLabel])
stackView.spacing = 0
stackView.alignment = .center
addSubview(stackView)
seatImageView.snp.makeConstraints { make in
make.size.equalTo(40)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
// MARK: - CUSTOM TITLE LABEL
class CustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.textColor = .darkGray
self.textAlignment = .left
self.numberOfLines = 4
}
}
Assuming you want all of the cells to be the same size...
One possible solution would be to use NSString.boundingRect. In a place where you only need to calculate this once, for example in viewDidLoad, determine the bounding rect of the largest text:
var maxTextWidth: CGFloat = 0
override func viewDidLoad() {
// All the other code you currently have, plus:
let boundingWidth = 9999 // Something arbitrarily large
let boundingHeight = 34 // Make this the height of however many lines you want
let targetSize = CGSize(width: boundingWidth, height: boundingHeight)
let attributes = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 17)] // Put any other necessary attributes to accurately calculate the size of the text
for vm in viewModel {
let width = NSString(string: vm.text).boundingRect(with: estimatedSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).width
if width > maxTextWidth {
maxTextWidthWidth = width
}
}
}
And then you also need to provide this size to the collection view using:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = maxTextWidth + // Any padding you want
let height = // Height of text plus any padding
return CGSize(width: width, height: height)
}
More documentation on boundingRect can be found here

Custom animated transition not working when using rootview

I am trying to create a custom transition, similar to the one that is on the App Store. I used these tutorials:
https://eric-dockery283.medium.com/custom-view-transitions-like-the-new-app-store-a2a1181229b6
https://www.raywenderlich.com/2925473-ios-animation-tutorial-custom-view-controller-presentation-transitions
I have also been experimenting with using the load view to have the root view to create the view, similar to what is used in the answer used here: does loadView get called even if we don't override it in viewcontroller like the other ViewController lifecycle methods?
now what I am doing is that I am using a collectionview, which I want to keep, and when an item is pressed it animate transitions to a new view controller with a larger image, simple.
the problem is that when I combine it with using the loadView with view = rootView() it did not work in some scenarios.
For example:
when I use the modalPresentationStyle = .fullScreen, when I press an item in the collection view the screen just goes black, and then when I press the view hierarchy it shows this:
when I remove the modalPresentationStyle = .fullScreen it does animate but it is the popover transition, which I do not want.
What I want is to have a full screen transition that is animated.
here is the view controller:
class ViewController: UIViewController {
let data = createData()
var transition = PopAnimator()
var rootView = MainView()
private let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.backgroundColor = .white
return collectionView
}()
private enum LayoutConstant {
static let spacing: CGFloat = 16.0
static let itemHeight: CGFloat = 200.0
}
override func loadView() {
view = rootView
rootView.collectionView.delegate = self
rootView.collectionView.dataSource = self
}
}
class MainView: UIView {
let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.backgroundColor = .white
return collectionView
}()
init() {
super.init(frame: .zero)
setupLayouts()
}
private func setupLayouts() {
backgroundColor = .white
addSubview(collectionView)
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.identifier)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),
collectionView.leftAnchor.constraint(equalTo: safeAreaLayoutGuide.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: safeAreaLayoutGuide.rightAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
cell.dogImg.image = UIImage(named: data[indexPath.row].image)
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selected = data[indexPath.row]
let vc = OtherViewController()
vc.transitioningDelegate = self
// vc.modalPresentationStyle = .fullScreen
vc.data = selected
self.present(vc, animated: true)
print(selected)
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = itemWidth(for: view.frame.width, spacing: LayoutConstant.spacing)
return CGSize(width: width, height: LayoutConstant.itemHeight)
}
func itemWidth(for width: CGFloat, spacing: CGFloat) -> CGFloat {
let itemsInRow: CGFloat = 2
let totalSpacing: CGFloat = 2 * spacing + (itemsInRow - 1) * spacing
let finalWidth = (width - totalSpacing) / itemsInRow
return floor(finalWidth)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: LayoutConstant.spacing, left: LayoutConstant.spacing, bottom: LayoutConstant.spacing, right: LayoutConstant.spacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
}
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard let selectedIndexPathCell = self.collectionView.indexPathsForSelectedItems?.first,
let selectedItem = self.collectionView.cellForItem(at: selectedIndexPathCell) as? CollectionViewCell
else { return nil }
guard let originFrame = selectedItem.superview?.convert(selectedItem.frame, to: nil) else {
return transition
}
transition.originFrame = originFrame
transition.presenting = true
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.presenting = false
return transition
}
}
this is the other view that is presented to.
class OtherViewController: UIViewController, UIViewControllerTransitioningDelegate {
var data: DogArr?
var rootView = testView1()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .systemBackground
}
override func loadView() {
view = rootView
//rootView.inputViewController?.transitioningDelegate = self
rootView.carImg.image = UIImage(named: data?.image ?? "")
rootView.dismissBtn.addTarget(self, action: #selector(dismissingBtn), for: .touchUpInside)
}
#objc func dismissingBtn() {
self.dismiss(animated: true)
}
}
class testView1: UIView {
var carImg: UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFit
img.translatesAutoresizingMaskIntoConstraints = false
return img
}()
var dismissBtn: UIButton = {
let btn = UIButton()
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Done", for: .normal)
btn.titleLabel?.font = UIFont.systemFont(ofSize: 25)
btn.titleLabel?.textAlignment = .center
btn.setTitleColor(.label, for: .normal)
btn.contentMode = .scaleAspectFill
return btn
}()
init() {
super.init(frame: .zero)
self.addSubview(carImg)
self.addSubview(dismissBtn)
carImg.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = .systemBackground
NSLayoutConstraint.activate([
carImg.topAnchor.constraint(equalTo: self.topAnchor),
carImg.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.5),
carImg.widthAnchor.constraint(equalTo: carImg.heightAnchor),
dismissBtn.topAnchor.constraint(equalTo: self.carImg.bottomAnchor, constant: 20),
dismissBtn.centerXAnchor.constraint(equalTo: self.dismissBtn.centerXAnchor),
dismissBtn.widthAnchor.constraint(equalToConstant: 150),
dismissBtn.heightAnchor.constraint(equalToConstant: 75)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I noticed that when I remove the root view and just created everything in the view controller, it seems to work, the thing is that this is just a test for now. The root view in a future project may be very large so I think keeping to a rootview may be a good idea. If there are any other methods similar, please share.
If there is anything I can answer please ask,
Thank you

UILabel SizeToFit Not working in UICollectionViewCell

This is my code
class DescriptionsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
let layout = TagFlowLayout()
layout.estimatedItemSize = CGSize(width: 140, height: 40)
collectionView.collectionViewLayout = layout
}
}
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 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 }
}
}
extension DescriptionsViewController : UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sourse.Ingredients.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell",for: indexPath) as? collectionViewCell else {
return collectionViewCell()
}
cell.label.text = sourse.Ingredients[indexPath.row] //[indexPath.section]
cell.label.preferredMaxLayoutWidth = collectionView.frame.width - 16
cell.label.sizeToFit()
return cell
}
} // uicollectionViewDatasourse,UICollectionViewDelegate
class collectionViewCell: UICollectionViewCell{
#IBOutlet weak var label: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius = label.frame.size.height / 2.0
self.backgroundColor = #colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)
}
} //UICollectionViewCell
The text will beyond the background color and the background color can't adaptation the text length
I added some code and answered my question.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: sourse.Ingredients[indexPath.item].size(withAttributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 17)]).width + 25, height: 30)
}
But I think it's not best answer because this source code can detection they just add .width + 25.
Obviously this code is not "dynamic", but it does work.
The problem exisit in your Xib constraints. When your label cannot get the right frame, sizeToFit will not work.
If you want the label adapt to your text length, you can try.
Snapkit
view.contentView.addSubview(label)
label.snp.makeConstraints {
$0.centerY.equalToSuperview()
$0.leading.trailing.equalToSuperview().inset(customOffset)
}
Xib
Juset set the same constraints, ___centeY, leading, trailing___, as by SnapKit(I am not familiar how to in Xib, so sorry about no cases)
Original Code
this may help
class tageCollectionViewCell: UICollectionViewCell{
var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
label = UILabel()
//... add your custom character
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
]
NSLayoutConstraint.activate(constraints)
}
}

UICollectionView cell not resizing based on cell content with autoresize

I am trying to create UICollectionView with cells that adjust the width based on the content of the cell using layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize but it doesn't update. Bellow is two images of what am trying to achieve and the next is what I have achieved.
Here is what am trying to achieve.
Correct display
Here is what I have been able to achieve
What I have now
And here is my code
import UIKit
struct Model {
let name: String
let color: String
}
class DisplayTagsView: UIView {
// Collection View
var projectTagsCollectionView: UICollectionView!
var projectTagsCollectionViewHeightConstraint: NSLayoutConstraint!
private var tagsObjects = [Any]() // Object to allow add button
private var tags: [Model] = [] // Tags
// MARK: - Init
init() {
super.init(frame: .zero)
initViews()
initTags()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func initTags() {
self.tags.append(Model(name: "Some tag", color: "color"))
self.tags.append(Model(name: "Twotwo", color: "color"))
self.tags.append(Model(name: "Smartparking", color: "color"))
self.tags.append(Model(name: "Development", color: "color"))
self.tagsObjects = self.tags
self.tagsObjects.append(AddButtonCollectionViewCell.self)
}
// MARK: - Private
private func initViews() {
initTagCollectionView()
}
private func initTagCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
layout.scrollDirection = .vertical
projectTagsCollectionView = UICollectionView(frame: self.frame, collectionViewLayout: layout)
projectTagsCollectionView.delegate = self
projectTagsCollectionView.dataSource = self
projectTagsCollectionView.register(cell: ProjectCollectionViewCell.self)
projectTagsCollectionView.register(cell: AddButtonCollectionViewCell.self)
projectTagsCollectionView.isScrollEnabled = false
projectTagsCollectionView.backgroundColor = .clear
}
// set up views
func setupView() {
addSubview(projectTagsCollectionView)
setCollectionHeight()
}
// update height
func setCollectionHeight() {
projectTagsCollectionViewHeightConstraint = projectTagsCollectionView.heightAnchor.constraint(equalToConstant: 200)
projectTagsCollectionViewHeightConstraint.isActive = true
}
// Button button
func addButton() -> UIButton {
let addButton = UIButton()
addButton.setImage(#imageLiteral(resourceName: "settings"), for: .normal)
return addButton
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension DisplayTagsView : UICollectionViewDelegateFlowLayout {
// Auto resizing of the cell tags
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout
var newEdgeInsets = UIEdgeInsets(top: flowLayout!.sectionInset.top, left: flowLayout!.sectionInset.left, bottom: flowLayout!.sectionInset.bottom, right: flowLayout!.sectionInset.right)
if collectionView.numberOfItems(inSection: section) == 1 {
let edgeInset = newEdgeInsets.right + (collectionView.frame.width - flowLayout!.itemSize.width)
newEdgeInsets.right = edgeInset
}
return newEdgeInsets
}
}
// MARK: - UICollectionViewDelegate
extension DisplayTagsView: UICollectionViewDelegate {
}
// MARK: - UICollectionViewDataSource
extension DisplayTagsView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tagsObjects.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let tagObject = self.tagsObjects[indexPath.item]
if let tag = tagObject as? Model {
let cell: ProjectCollectionViewCell = projectTagsCollectionView.dequeueTypedCell(forIndexPath: indexPath)
cell.configureCell(tagName: tag.name, tagBackground: tag.color)
return cell
} else {
// Adding button to the UICollection
let cell: AddButtonCollectionViewCell = membersCollectionView.dequeueTypedCell(forIndexPath: indexPath)
let addButto = addButton()
cell.contentView.subviews.forEach({$0.removeFromSuperview()})
cell.contentView.addSubview(addButton)
settingButton.snp.makeConstraints { make in
make.size.equalTo(cell.contentView)
}
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
}
}
Try Following library. TagList view.
https://github.com/ElaWorkshop/TagListView. It surely working ur conditions

SubViews are not adding in some UICollectionViewCells and flickering (programmatically)

I am trying to make custom Image Slider with collections view. I want to it to be reusable. So I made separate custom class where all collectionView stuff. and then call that class from UIViewController as shown in code below. And my UICollectonViewCell only contains imageView.
Problem is that in second cell. imageView is not being added and on third cell, it also flickers. I tried to debug these issues but could not.
ImageSlider class and UICollectionViewCell class at end end, with collection view stuff:
class ImageSlider: UIView {
var imgArr = [UIImage(named: "one.jpg"), UIImage(named: "3.jpg"), UIImage(named: "two.jpg"), UIImage(named: "4.jpg")]
var sliderCollection : UICollectionView = {
let widthRatio : Float = 16.0
let heightRatio : Float = 9.0
let collecionWidth = UIScreen.main.bounds.width - 30
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: 15, y: 100, width: collecionWidth, height: CGFloat((Float(collecionWidth)*heightRatio)/widthRatio)), collectionViewLayout: layout)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView.backgroundColor = .systemOrange
collectionView.isPagingEnabled = true
// collectionView.isScrollEnabled = true
collectionView.register(ImageSliderCell.self, forCellWithReuseIdentifier: "ImageSliderCell")
return collectionView
}()
}
extension ImageSlider: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! ImageSliderCell
cell.imgView.image = imgArr[indexPath.item]
// cell.imgView.contentMode = .scaleAspectFit
print("cell frame : ", "(\(cell.frame.width), \(cell.frame.height)")
print("imgView frame : ", "(\(cell.imgView.frame.width), \(cell.imgView.frame.height)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
// override func awakeFromNib() {
// self.addSubview(imgView)
// }
override init(frame: CGRect) {
super.init(frame: frame)
imgView.frame = frame
self.addSubview(imgView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is ViewController, where I am calling ImageSlider() class.
class ImageSliderVC: UIViewController, UICollectionViewDelegate {
let imageSlider = ImageSlider()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageSlider.sliderCollection)
imageSlider.sliderCollection.delegate = imageSlider
imageSlider.sliderCollection.dataSource = imageSlider
imageSlider.sliderCollection.reloadData()
}
}
It looks like it does not work without constrains because UICollectionViewCell could be created with zero frame and it translated to imageView inside the cell. You need to add constrains to imageView to make it visible.
extension UIView {
func centerX(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imgView)
// not sure about the right size of image ...
imgView.setDimensions(height: 100.0, width: 100.0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources