Add UILabels with different width side by side programmatically - swift

I want to add multiple UILabels programmatically side by side to a TableViewCell. The UILabels have different width.
The first cell in the picture shows the problem and the second cell what I would like to do.
In this example I want to add four UILabels to a TableViewCell. But the width of the TableViewCell is smaller than the width of the UILabels. Therefore I must increase the CellHeight and add the UILabels below to the other UILabels (like the second cell in the picture).

You should put UICollectionView inside of a row of your UITableViewCell.
Each cell of your UICollectionView will have a multi UILabel. Update the dataSource for your UICollectionView depending on your label count. Set isScrollEnabled false of that UICollectionView and set automatic row height for UITableViewCell.
Also, set flow layout to UICollectionView :
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSizeMake(1, 1) }
Adjust cell size like below:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let size: CGSize = keywordArray[indexPath.row].size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 14.0)])
return CGSize(width: size.width + 45.0, height: keywordsCollectionView.bounds.size.height)
}

At first you have to make for labels.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
let label1 = UILabel(frame: CGRect(x: 5, y: 5, width: 100, height: 25))
label1.text = "Label 1"
let label2 = UILabel(frame: CGRect(x: 110, y: 5, width: 225, height: 25))
label2.text = "Label 2"
let label3 = UILabel(frame: CGRect(x: 5, y: 40, width: 100, height: 25))
label3.text = "Label 3"
let label4 = UILabel(frame: CGRect(x: 110, y: 40, width: 225, height: 25))
label4.text = "Label 4"
cell.addSubview(label1)
cell.addSubview(label2)
cell.addSubview(label3)
cell.addSubview(label4)
return cell
}
Each label has different width and different position. You can play with it

Related

tableView Cell Labels Stack When I Scroll

I have a custom tableView, entirely created in code. IE, the cells need to be in code too.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inventoryCell", for: indexPath)
let namelabel = UILabel()
namelabel.frame = CGRect(x: 145, y: 10 , width: 200, height: 30 )
namelabel.text = itemToShow.name
namelabel.font = UIFont.boldSystemFont(ofSize: 20)
namelabel.backgroundColor = .clear
cell.addSubview(namelabel)
let detailLabel = UILabel()
detailLabel.frame = CGRect(x: 145, y: 50 , width: 200, height: 50 )
detailLabel.text = itemToShow.detail
detailLabel.backgroundColor = .clear
cell.addSubview(detailLabel)
let inventoryImage = UIImageView()
inventoryImage.frame = CGRect(x: 10, y: 10, width: 130, height: 130)
inventoryImage.image = UIImage(named: "emptyInventorySlot")
cell.addSubview(inventoryImage)
return cell
}
It works great, and you can see it loads perfectly. The top image is the load, the bottom image is once I scroll to the bottom. You can see the text labels seem to all stack on top of each other.
I would not recommend doing it this way.
It's better to move the code that creates namelabel, detailLabel and inventoryImage to an inventoryCell class derived from UITableViewCell.
But you can make it work.
Since table cells are reused and the reused cells already contain the created subviews, you need to either remove the subviews or treat cells with already created subviews differently.
And you should place your labels in the contentView of your UITableCellView.
To make your code work you can do this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inventoryCell", for: indexPath)
// remove any subviews from contentView from recycled cells
cell.contentView.subviews.forEach{ $0.removeFromSuperview() }
let namelabel = UILabel()
namelabel.frame = CGRect(x: 145, y: 10 , width: 200, height: 30 )
namelabel.text = itemToShow.name
namelabel.font = UIFont.boldSystemFont(ofSize: 20)
namelabel.backgroundColor = .clear
// add any label to contentView of the cell
cell.contentView.addSubview(namelabel)
let detailLabel = UILabel()
detailLabel.frame = CGRect(x: 145, y: 50 , width: 200, height: 50 )
detailLabel.text = itemToShow.detail
detailLabel.backgroundColor = .clear
// add any label to contentView of the cell
cell.contentView.addSubview(detailLabel)
let inventoryImage = UIImageView()
inventoryImage.frame = CGRect(x: 10, y: 10, width: 130, height: 130)
inventoryImage.image = UIImage(named: "emptyInventorySlot")
// add any label to contentView of the cell
cell.contentView.addSubview(inventoryImage)
return cell
}
Better would be:
create a UITableViewCell subclass
place your label placement and configuration there
take care of cell reuses, override prepareForReuse() to support cell reuse
register this class as a table view cell for your tableView
cast your dequeued cell to your UITableViewCell subclass

How to make tableview row and label height according to label text in swift?

I am making chat related screen, here i have placed view inside tableview cell and label in side view.
for view and i have given leading, trailing, top, bottom
for label i have given leading, trailing, top, bottom constraints
but here cell and label text not coming well, sometime for short text cell becomes big and for long text it remains same like below its coming
here is the code:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if indexPath.section==0 {
if (indexPath.row%2)==0
{
let cell:ReceiverChatTableViewCell = (chatTableView.dequeueReusableCell(withIdentifier: "ReceiverChatTableViewCell") as! ReceiverChatTableViewCell?)!
let font = UIFont(name: "Helvetica", size: 20.0)
var height = heightForView(text: cell.receiverTextLbl.text!, font: font!, width: 100.0)
if height<60
{
height=60
}
return height
}
}
else
{
return 50
}
return UITableView.automaticDimension
}
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 1000
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
please help me to solve the code.

UICollectionView Items Data distorts after scrolling in Swift 4.2

I am trying to implement a chat screen using UICollectionView and data is displayed as expected. However when I try to scroll it a few times my data gets distorted as explained in the screenshots. Can anyone suggest what's going wrong and how to solve it? Thanks!
First it shows:
After scrolling a few times it shows:
Code of all the methods related to UICollectionView I'm using:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let count = chatCategoriesArray.messages.count
if count != 0 {
return count
}
return 0
}
var allCellsHeight: CGFloat = 0.0
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatLogMessageCell
cell.messageTextView.text = chatCategoriesArray.messages[indexPath.item]
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: chatCategoriesArray.messages[indexPath.item]).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18)], context: nil)
if chatCategoriesArray.senderIds[indexPath.item] == "s_\(self.studentInstance.tutorIdFound)"{
cell.profileImageView.image = UIImage(named: "three.png")
cell.profileImageView.isHidden = false
cell.messageTextView.frame = CGRect(x: 48 + 8, y:0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x: 48, y: 0, width: estimatedFrame.width + 16 + 8, height: estimatedFrame.height + 20)
self.currentCellWidth = Double(estimatedFrame.width + 16 + 8)
cell.textBubbleView.backgroundColor = .white
cell.addSubview(cell.profileImageView)
cell.addConstraintsWithFormat(format: "H:|-8-[v0(30)]", views: cell.profileImageView)
cell.addConstraintsWithFormat(format: "V:[v0(30)]|", views: cell.profileImageView)
}
else{
cell.profileImageView.image = UIImage(named: "two.png")
cell.textBubbleView.backgroundColor = UIColor(r: 28, g:168, b:261)
cell.messageTextView.frame = CGRect(x: view.frame.width - estimatedFrame.width - 16 - 46, y:0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 20)
cell.messageTextView.textColor = .white
cell.textBubbleView.frame = CGRect(x: view.frame.width - estimatedFrame.width - 16 - 8 - 46, y: 0, width: estimatedFrame.width + 16 + 8, height: estimatedFrame.height + 20)
self.currentCellWidth = Double(estimatedFrame.width + 16 + 8)
cell.addSubview(cell.profileImageView)
cell.addConstraintsWithFormat(format: "H:[v0(30)]-8-|", views: cell.profileImageView)
cell.addConstraintsWithFormat(format: "V:[v0(30)]|", views: cell.profileImageView)
}
allCellsHeight += cell.frame.height
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if allCellsHeight < (collectionView.frame.height){
return UIEdgeInsets(top: view.frame.height - allCellsHeight, left: 0, bottom: 0, right: 0)
}
else {
return UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: chatCategoriesArray.messages[indexPath.item]).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20)
}
Collection cells are reused. That means that when you scroll up/down, the cells that become invisible are removed from hierarchy and queued for reusing. Then the collection view calls cellForItem: again for the items that become visible. dequeueReusableCell does not always create a new instance. Usually it will only return a cell that has become invisible for you to setup it again with new data.
If you add views/constraints during setup, you have to make sure to remove the ones you have added previously, otherwise the cell will have duplicate views and conflicting constraints.
Also note that allCellsHeight cannot work like this. cell.frame.height won't be correct immediately after setup (before actual layout) and since the method can be called several times for the same item, you cannot just add to a global variable. You should rather use collectionView.contentSize.height instead.
This is classic reusable cell trouble. This happens cause collection view reuse your receiver cells setting to create sender message box.
I will suggest you to use two different cell for sender and receiver. With constraint set on first load. This will have a positive impact on performance as well.
Check following image to understand how to use 2 cell.

UICollectionViewCell dashed border for dynamic cell width swift issue

Using UICollectionView under the UITableViewCell
Using below extension for creating dashed border:
extension UIView {
func addDashedLineView(frame: CGRect) {
let border = CAShapeLayer()
border.strokeColor = UIColor.blue.cgColor
border.lineDashPattern = [6, 6]
border.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)//self.frame
border.fillColor = UIColor.clear.cgColor
border.lineJoin = kCALineJoinRound
//print("widht: \(frame.width) fraheight: \(UIScreen.main.bounds.width)")
border.fillColor = nil
border.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: frame.width, height: frame.height), cornerRadius: 6).cgPath
self.clipsToBounds = true
self.layer.addSublayer(border)
}
}
Below is my collection view method where i am calling above method (actually the cell width depending upon the content size so i am calculating the text width) the issue is when the cell are dequeue from the list the border get redraw from different width, also i am using custom cell "CollectionViewCell" class:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let width = self.collectionData[indexPath.row].width(withConstraintedHeight: 14, font: .systemFont(ofSize: 17)) + Constant.cellPadding
cell.contentView.addDashedLineView(frame: CGRect(x: 0, y: 0, width: width, height: Constant.itemHeight))
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
cell.layoutIfNeeded()
return cell
}
When first load drawing border correctly when scroll the view it has been redraw
How to resolve the issue

UICollectionView's Header's height dependent on its content in Swift

Inside a UICollectionView I have a HeaderView within a UILabel. numberOfLines is set to zero to change label's height based on its text. I want the header's height to depend on the label's frame.
PS.
HeaderView in UICollectionView is not as simple as a common view or even UITableViewCell. This feature works with them pretty easy unlike with UICollectionReusableView.
Find the height of your label using this function:-
func labelHeight(width:CGFloat , font:UIFont , text:String)->CGFloat{
let label:UILabel = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
add this UICollectionViewDelegateFlowLayout method into your controller and find the height and return header size here
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize{
let height = labelHeight(width: collectionView.bounds.width, font:pass the labelFont here , text:"Pass label text here")
return CGSize(width:collectionView.bounds.width , height: height + 10)
}