swift - Correct way to pass data to UICollectionViewCell - ios

I'm trying to create my own Book app, and using UICollectionView for listing all the books. Data for each cell is from .plist file, and I'm using custom flowLayout(to make some changes later).
So now I'm stucked with delays and lags when scrolling. I suppose I've made mistakes with passing data to cell or with cell initializing.
Cell created by .xib and custom class, just some layout and UI:
class BookCoverCell: UICollectionViewCell {
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var darkRedView: UIView!
#IBOutlet weak var lightRedView: UIView!
#IBOutlet weak var readButton: UIButton!
#IBOutlet weak var pageLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
clipsToBounds = true
self.backgroundColor = UIColor(white: 1, alpha: 0.0)
view1.layer.cornerRadius = 10
view2.layer.cornerRadius = 10
darkRedView.layer.cornerRadius = 10
lightRedView.layer.cornerRadius = 10
}
}
At ViewController's class:
class MainVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate {
#IBOutlet weak var collectionContainerView: UIView!
#IBOutlet weak var collectionView: UICollectionView!
var books: Array<Book>? {
didSet {
collectionView.reloadData()
}
}
var flowLayout = UICollectionViewFlowLayout()
override func viewDidLayoutSubviews() {
flowLayout = ZoomAndSnapFlowLayout()
collectionView.collectionViewLayout = flowLayout
}
override func viewDidLoad() {
super.viewDidLoad()
collectionContainerView.backgroundColor = UIColor(white: 1, alpha: 0.0)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "BookCoverCell", bundle: nil), forCellWithReuseIdentifier: "BookCoverCell");
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
books = BookStore.sharedInstance.loadBooks(plist: "Books")
}
//MARK: UICollectionViewDelegate
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let books = books {
return books.count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCoverCell", for: indexPath) as! BookCoverCell
let book = books![indexPath.row]
let cover = book.coverImage()!
let color = book.getDominantColor()
cell.view1.backgroundColor = color
cell.imageView.image = cover
cell.pageLabel.text = "Pages: 29"
cell.readButton.setTitle("Read", for: .normal)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let book = books?[indexPath.row]
print ("open")
}
}
And my layout now looks like:
class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout {
var cellWidth = CGFloat()
var cellHeight = CGFloat()
var minLineSpacing = CGFloat()
override init() {
super.init()
self.scrollDirection = .horizontal
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
guard let collectionView = collectionView else { fatalError() }
cellHeight = collectionView.frame.height * 0.8
minLineSpacing = 200
cellWidth = cellHeight
itemSize = CGSize(width: cellWidth, height: cellHeight)
let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2
let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2
sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets)
super.prepare()
}
}
So I'm pretty sure that have some mistakes in my code, but can't find them(( Any suggestion will be helpful for me!
UPDATE:
So first of all thanks!
I've changed my code and replace a really big image with smallest one, and function to get dominant color simply to white color for now, and things already gets better!
BUT there is small lag or delay when first scroll begins, and only with 1st and 2nd cells, while they are scrolling to the left edge of the screen. And after that all cells scrolling without lags, even first two, in both directions (left / right).
Now my code looks like:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCoverCell", for: indexPath) as! BookCoverCell
let book = books![indexPath.row]
let cover = UIImage(named: "flag.png")
let color = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
cell.view1.backgroundColor = color
cell.imageView.image = cover
cell.pageLabel.text = "Pages: 29"
cell.readButton.setTitle("Read", for: .normal)
return cell
}
and UPDATE #2
removing
flowLayout = ZoomAndSnapFlowLayout()
collectionView.collectionViewLayout = flowLayout
to viewWillAppear() fix these small lags too!! Hooray!)

Read this article about Time Profiling and collection view: https://voxels.github.io/eliminating-collection-view-tearing-with-xcode-time-profiler-instrument
Since your implementation is very simple, there aren't many things that could be wrong, but you probably have the same issue that the author of the article had -- the images themselves are very large, and need to be read and resized.
They solved the issue by making appropriately sized versions of the images.
In your case getDominantColor will also be slower on large images (I am assuming that it reads the pixels to get the dominant color. You should also consider caching this color and not recalculating it every time (if you are not already doing that).

Related

Loading different heights for cell with UICollectionView

I am trying to dynamically adjust cells in a CollectionView according to the contents of a label. Basically i have found how to but my challenge is
cellForItemAt method
gets called first before
heightForCellAtIndexPath method
and by doing so this means we are trying to get heights before data is loaded so the cells will always be the same height and therefore we wont achieve different heighted cells
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let content_ = content[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NotesUI", for: indexPath) as! NotesUI
cell.setData(data: content_)
return cell
}
func collectionView(_ collectionView: UICollectionView, heightForCellAtIndexPath indexPath:IndexPath) -> CGFloat {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NotesUI", for: indexPath) as! NotesUI
return cell.allNoteViews()[indexPath.item - 1].bounds.size.height
}
Essentially in the NotesUI CollectionView class:
class NotesUI:UICollectionViewCell{
#IBOutlet var note: UILabel!
var views: [UILabel] = []
func setData(data: NoteModel){
note.text = data.note
note.numberOfLines = 0
note.lineBreakMode = NSLineBreakMode.byWordWrapping
note.sizeToFit()
views.append(note)
}
func allNoteViews()->[UILabel]{
return views
}
}
I guess my question boils down to how can i call heightForCellAtIndexPath method after the cellForItemAt method so that the CollectionView gets the height of the label after it has contents
You don't need to calculate each cell height if you use auto layout. You can add a vertical stack view to your cell and add your labels to it.
Here is a basic example of self-sizing collection view cells.
class YourCustomCell: UICollectionViewCell {
// Add your labels to this stackView
#IBOutlet weak var yourLabelContainer: UIStackView!
override func awakeFromNib() {
super.awakeFromNib()
yourLabelContainer.translatesAutoresizingMaskIntoConstraints = false
yourLabelContainer.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width - 32).isActive = true // for 16 - 16 padding
}
}
class YourViewController: UIViewController {
#IBOutlet weak var yourCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
yourCollectionView.delegate = self
yourCollectionView.dataSource = self
let nib = UINib(nibName: "YourCustomCell", bundle: nil)
yourCollectionView.register(nib, forCellWithReuseIdentifier: "cellReuseId")
if let layout = yourCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
}

CollectionView reloadData() weird behaviour

I want to make a Collection View with cells, where you can select the colors of my little game. Therefor you can choose between different themes (each in one cell). If the theme is unlocked (by achieving scores), the blur view and the lock-icon should disappear, and all available cells/ themes should be selectable. I implemented this like so:
(Collection ViewController Class:)
import UIKit
import PMSuperButton
class myCollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var backBtn: UIButton!
let items = ["Default dot", "Trap your first dot", "Score 15 or less (classic)", "Score 5 or less (classic)", "Score 15 or less (hard)", "Score 25 or less (no chill)", "Score 15 or less (snake)", "Score 15 or less everywhere", "Score 10 or less everywhere", "Circle the dot 100 times", "10"]
var preSelected = IndexPath()
#IBOutlet weak var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
updateUnlockedString()
updateColor()
}
func updateColor(){
view.backgroundColor = myColors().selected(value: selectedInstance.selectedColor).backgroundColor
}
func updateUnlockedString(){
let easyHS = UserDefaults.standard.integer(forKey: "highscoreeasy")
let classicHS = UserDefaults.standard.integer(forKey: "highscoreclassic")
let hardHS = UserDefaults.standard.integer(forKey: "highscorehard")
let nochillHS = UserDefaults.standard.integer(forKey: "highscoreno chill")
let snakeHS = UserDefaults.standard.integer(forKey: "highscoresnake")
var unlockedString = ""
//unimportant steps deleted
UserDefaults.standard.set(unlockedString, forKey: "unlocked")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! myCollectionViewCell
if(unlockInstance.unlockedString.contains(String(indexPath.item))){
cell.unlockedEffect()
print("unlocked Effect")
}
cell.label.text = items[indexPath.item]
cell.removeHighlight()
if indexPath.item == selectedInstance.selectedColor{
cell.highlightEffect()
preSelected = IndexPath(item: indexPath.item, section: indexPath.section)
}
cell.setupCellFilling(playerColor: myColors().selected(value: indexPath.item).playerColor, objColor: myColors().selected(value: indexPath.item).objColor, defaultColor: myColors().selected(value: indexPath.item).defaultColor, backgroundColor: myColors().selected(value: indexPath.item).backgroundColor)
myCollectionView = collectionView
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.allowsMultipleSelection = false
if ((collectionView.cellForItem(at: indexPath) as! myCollectionViewCell?) != nil) && unlockInstance.unlockedString.contains(String(indexPath.item)){
selectedInstance.selectedColor = indexPath.item
updateColor()
collectionView.reloadData()
}
}
#IBAction func backPressed(_ sender: Any) {
cameInstance.came = true
}
}
(Collection CELL Class:)
import UIKit
class myCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var view: UIView!
#IBOutlet weak var blurView: UIVisualEffectView!
#IBOutlet weak var lockedBlur: UIVisualEffectView!
#IBOutlet weak var lockedIcon: UIImageView!
override func layoutSubviews() {
// cell rounded section
self.layer.cornerRadius = 15.0
self.layer.masksToBounds = true
// cell shadow section
self.contentView.layer.cornerRadius = 15.0
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = myColors().selected(value: selectedInstance.selectedColor).defaultColor.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
self.layer.shadowRadius = 6.0
self.layer.shadowOpacity = 0.4
self.layer.cornerRadius = 15.0
self.layer.masksToBounds = false
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.contentView.layer.cornerRadius).cgPath
super.layoutSubviews()
}
extension myCollectionViewCell{
func highlightEffect(){
self.layer.borderWidth = 5.0
self.layer.borderColor = UIColor.systemGreen.cgColor
}
func removeHighlight(){
self.layer.borderColor = UIColor.clear.cgColor
}
func unlockedEffect(){
lockedBlur.alpha = 0
lockedIcon.alpha = 0
}
func setupCellFilling(playerColor: UIColor, objColor: UIColor, defaultColor: UIColor, backgroundColor: UIColor){
lockedBlur.effect = UIBlurEffect(style: .extraLight)
blurView.effect = UIBlurEffect(style: .systemUltraThinMaterialDark)
lockedIcon.tintColor = objColor
view.backgroundColor = backgroundColor
}
}
The problem
When I open the CollectionView (viewDidLoad), everything appears 100% fine! All cells that should be locked, appear locked (lockedBlur.alpha = 0 and lockedIcon.alpha = 0). Also, only the locked cells are selectable, which I can see with the green border. Only the selected Cell has a green border, thats right.
BUT: When I select a cell, some random locked cells loose their blur view and loose their lock-icon (so blockedBlur and lockedIcon disappear). They still aren't selectable. Sometimes this affects all cells, sometimes only e.g. 2.
When I select a cell, the didSelectItemAt Function gets called. And this also calls updateColors() which only changes the background and calls the mysterious reloadData(). In my opinion the reloadData() SOMEHOW makes the lockedBlur and lockedIcon disappear, and I cannot find why....
Maybe someone can help me..
Thank you!!
SwiftHobby
I am working on Fitness App which has pretty similar working CollectionView behavior for it's training plan section. Basically you select certain cell when you finish an exercise. So I played around a lot with way of implementing selecting and deselecting cells. As I see you are missing selecting and deselecting functions of CollectionViewDelegate. There you set different states Effects
Here is the functions you have to use:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell {
cell.showIcon()
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell {
cell.hideIcon()
}
}

How do I make image visible when collection view cell is double tapped?

I have a collection view with a lot of images and all of these images has a heart image in the right corner. This heart image needs to be set to visible when the big image is double tapped as an indicator that it has been liked.
I have added a double tap gesture to my collection view and now I need to set the heart image to visible when this gesture happens on the selected cell.
Any suggestions to how I do it? I can't find any answers to this anywhere.
This is my collection view controller:
import UIKit
class OevelserCollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
// MARK: - Properties
#IBOutlet weak var OevelserCollectionView: UICollectionView!
#IBOutlet var tap: UITapGestureRecognizer!
var oevelseCollectionViewFlowLayout: UICollectionViewFlowLayout!
let oevelseArray = OevelseArray()
// MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
setupOevelseCollectionView()
}
// MARK: - Functions
#IBAction func didDoubleTap(_ sender: UITapGestureRecognizer) {
print("tapped")
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
setupOevelseCollectionViewItemSize()
}
private func setupOevelseCollectionView() {
self.OevelserCollectionView.delegate = self
self.OevelserCollectionView.dataSource = self
let nib = UINib(nibName: "OevelseCollectionViewCell", bundle: nil)
OevelserCollectionView.register(nib, forCellWithReuseIdentifier: "OevelseCollectionViewCell")
}
private func setupOevelseCollectionViewItemSize() {
if oevelseCollectionViewFlowLayout == nil {
let numberOfItemPerRow: CGFloat = 1
let lineSpacing: CGFloat = 20
let interItemSpacing: CGFloat = 8
let width = (OevelserCollectionView.frame.width - (numberOfItemPerRow - 1) * interItemSpacing) / numberOfItemPerRow
let height = width - 50
oevelseCollectionViewFlowLayout = UICollectionViewFlowLayout()
oevelseCollectionViewFlowLayout.itemSize = CGSize(width: width, height: height)
oevelseCollectionViewFlowLayout.sectionInset = UIEdgeInsets.zero
oevelseCollectionViewFlowLayout.scrollDirection = .vertical
oevelseCollectionViewFlowLayout.minimumLineSpacing = lineSpacing
oevelseCollectionViewFlowLayout.minimumInteritemSpacing = interItemSpacing
OevelserCollectionView.setCollectionViewLayout(oevelseCollectionViewFlowLayout, animated: true)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return oevelseArray.oevelser.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OevelseCollectionViewCell", for: indexPath) as! OevelseCollectionViewCell
let oevelseText = oevelseArray.oevelser[indexPath.item].oevelseName
let oevelseImage = oevelseArray.oevelser[indexPath.item].oevelseImage
cell.oevelseLabel.text = oevelseText
cell.oevelseImageView.image = UIImage(named: oevelseImage)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
And here is my collection view cell class:
import UIKit
class OevelseCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var oevelseImageView: UIImageView!
#IBOutlet weak var oevelseLabel: UILabel!
#IBOutlet weak var isLikedImageView: UIImageView!
#IBOutlet weak var heartImageWidthConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
}
}
Inside cellForRowAt
cell.oevelseImageView.image = UIImage(named: oevelseImage)
cell.oevelseImageView.tag = indexPath.row
let tapGR = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tapGR.numberOfTapsRequired = 2
cell.oevelseImageView.addGestureRecognizer(tapGR)
#objc func handleTap(_ gesture: UITapGestureRecognizer){
let index = gesture.view!.tag
guard let cell = tableView.cellForRow(at:IndexPath(row:index,section:0)) else { return }
arr[index].isLiked.toggle()
cell.isLikedImageView.image = arr[index].isLiked ? <#likeImg#> : <#defImg#>
}
OR
#objc func handleTap(_ gesture: UITapGestureRecognizer){
arr[gesture.view!.tag ].isLiked.toggle()
self.tableView.reloadRows(at:[IndexPath(row:index,section:0)],with:.none)
}

How to NOT reuse cells in UICollectionView - Swift

I have UICollectionView in a cell of UITableView. This UICollectionView displays user's profile photos. There are only a few items there (let's assume 10 photos is a maximum).
So, I do not want to reload images from backend every time cell reusing. It's inconvenient and unfriendly UX. Therefore, I want to create 10 cells, load images into them and show.
But I don't know how to create (instead of dequeuing) new custom cells within "cellForItem" method of UICollectionView. Or how to forbid reusing.
I'm a kind of newbie in programming, so I would be glad if you explained me that in simple words and most detailed. Thank you.
Here is my code:
Setting CollectinView (uploading random local photos in testing purpose):
extension PhotosTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AddPhotoCollectionViewCell", for: indexPath)
return cell
} else {
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCollectionViewCell", for: indexPath) as! PhotoCollectionViewCell
cell.photo = UIImage(named: "Photo\(Int.random(in: 1...8))") ?? UIImage(named: "defaultPhoto")
cell.layer.cornerRadius = 10
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = collectionView.frame.height
let width = (height / 16.0) * 10.0
return CGSize(width: width, height: height)
}
}
My custom PhotoCollectionViewCell:
class PhotoCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
var photo: UIImage! {
didSet {
// simulate networking delay
perform(#selector(setImageView), with: nil, afterDelay: Double.random(in: 0.2...1))
// setImageView()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#objc private func setImageView() {
let cellHeight = self.frame.height
let cellWidth = self.frame.width
let cellRatio = cellHeight / cellWidth
let photoRatio = photo.size.height / photo.size.width
var estimatedWidth = cellWidth
var estimatedHeight = cellHeight
if photoRatio > cellRatio {
estimatedWidth = cellWidth
estimatedHeight = cellWidth * photoRatio
} else {
estimatedHeight = cellHeight
estimatedWidth = cellHeight / photoRatio
}
widthConstraint.constant = estimatedWidth
heightConstraint.constant = estimatedHeight
imageView.image = photo
}
override func prepareForReuse() {
imageView.image = nil
}
}
Just create, fill and return new cell object in cellForItemAt instead of dequeueing it, but it's not a qood practice and you shouldn't do that.
You need to create new cell each time and assign the value to it. Although this is very dangerous thing to do and can cause your app to take so much memory over use your app will use more power and make the device slower.
Instead I suggest that you save your data into a Collection and to keep reusing it as you go.

Found nil while setting value of a label inside Collection view cell

Case:
I'm using a collection view inside a UIView, I've connected it as an outlet called 'partsCollectionView'. I've created a cell with identifier 'cell' and a custom class 'SelectionCollectionViewCell' for that cell. Inside the cell, I've got a label called 'cellTitle'.
Error: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I'm facing the error while setting the value of that label inside 'cellForItemAt' Method.
Here are the concerned views:
Cell description,
and, collection View Description
The Cell Class is:
import UIKit
class SelectionCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var cellTitle: UILabel!
}
The class where I'll use the collection view is:
import UIKit
private let reuseIdentifier = "cell"
let array = ["small","Big","Medium","Very big","Toooo big String","small"]
class SelectionCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var partsCollectionView: UICollectionView!
#IBOutlet weak var instructionsView: UITextView!
#IBOutlet weak var image: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.partsCollectionView.register(SelectionCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = partsCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SelectionCollectionViewCell
cell.layer.cornerRadius = 10
cell.clipsToBounds = true
cell.cellTitle.text = array[indexPath.row]
cell.layer.borderColor = #colorLiteral(red: 0.07843137255, green: 0.6862745098, blue: 0.9529411765, alpha: 0.6819349315)
cell.layer.borderWidth = 2
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let name = array[indexPath.row]
let attributedName = NSAttributedString(string: name)
let width = attributedName.widthWithConstrainedHeight(height: 20.0)
return CGSize(width: width + 40, height: 30.0)
}
}
extension NSAttributedString {
func widthWithConstrainedHeight(height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return boundingBox.width
}
}
Update: This is how it looks if I skip setting the title text. The title will come inside those rounded boxes.
Remove this line from viewDidLoad.
self.partsCollectionView.register(SelectionCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
When you register a cell programmatically it creates a new collection view cell object. It doesn't get the cell from storyboard. So cellTitle will be nil
OR
Programmatically initialize the label in custom collection view cell
class SelectionCollectionViewCell:UICollectionViewCell {
let cellTitle = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(cellTitle)
cellTitle.frame = CGRect(x: 10, y: 10, width: 100, height: 30)
}
}
Just minor mistake is there I think, In cellForItemAt indexPath Please update following line,
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SelectionCollectionViewCell
I just paste your code and got following output, nothing else,
If you still unable to produce desired output, just delete existing UILabel from SelectionCollectionViewCell and add it again.
FYI. No need to register cell in viewDidLoad() method

Resources