I have a custom UICollectionViewCell which I am overwriting. It has a property called nameLabel of type UILabel. I set initialize it without a frame and set the numberOfLines to 3.
let nameLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 3
label.sizeToFit()
return label
}()
The name label has the following VFL constraint applied to it:
addConstraintsWithFormat("V:[v0]-10-[v1]-10-|", views: nameLabel, userLabel)
where addConstraintsWithFormat is defined as the following from Brian Voong's UICollectionView example:
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerate() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
What happens is, say I have one cell whose nameLabel text is rendered in three lines (enough lines to merit three lines of wrapping, given the constraints).
So what happens is, I scroll to that cell, it shows the nameLabel as having three lines, I scroll past that cell. When I scroll back up to that cell, the very same UILabel now has one line.
How can I keep the flexible display of the UILabel size based on the content it renders?
I was able to reproduce your error, implementing it the way you have above makes the error...
I didn't see how you implemented it inside your -cellForItemAtIndexPath though, Fixed it with some modification from what you have provided, here is my -cellForItemAtIndexPath looks like:
//--
// UICollectionView Delegates
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cvCell", forIndexPath: indexPath)
// just to remove multi existence of identical views
//
cell.subviews.forEach { (view) in
view.removeFromSuperview()
}
let nameLabel: UILabel = {
let label = UILabel()
// declare constant configurations here
//
label.numberOfLines = 3
label.textAlignment = NSTextAlignment.Center
label.text = "Index: \(indexPath.row)\n and this is quite a long text that will occuply 3 lines"
return label
}()
// this is just an example base frame
//
nameLabel.frame = CGRectMake(0, 0, cell.frame.size.width/*your width*/, CGFloat.max)
nameLabel.sizeToFit()
cell.addSubview(nameLabel)
// ...
//
// updates constraints here
return cell
}
Hope this would help you.. Cheers! :)
I ended up solving the error but setting Visual Format Strings of Auto Layout Constraints.
I hadn't applied the horizontal constraint, neither had I given the UILabel a fixed width. My new constraints are the following:
addConstraintsWithFormat("H:|-20-[v0(200)]", views:nameLabel)
addConstraintsWithFormat("V:[v0]-10-[v1]-10-|", views: nameLabel, userLabel)
where as before I only had
addConstraintsWithFormat("V:[v0]-10-[v1]-10-|", views: nameLabel, userLabel)
I didn't use sizeToFit() at all. Only the following were my UILabel settings in my UICollectionViewCell class.
let nameLabel: UILabel = {
let label:UILabel = UILabel(frame: CGRectMake(0, 0, 200, 200))
label.numberOfLines = 3
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
let font = UIFont(name: "HelveticaNeue-Bold", size: 20.0)
label.font = font
return label
}()
and then I set the text later in the class
nameLabel.text = name
Related
I want my cells to have dynamic height. I use the below code:
let tableView: UITableView = {
let view = UITableView()
view.register(MyTableViewCell.self, forCellReuseIdentifier: MyTableViewCell.reuseIdentifier)
view.rowHeight = UITableView.automaticDimension
view.estimatedRowHeight = 150
view.separatorStyle = .singleLine
view.isScrollEnabled = true
return view
}()
The cell contains only label that is given one constraint- to be centered inside a cell:
private func setupView() {
addSubview(titleLabel)
titleLabel.snp.makeConstraints { maker in
maker.center.equalToSuperview()
}
}
the label's definition:
let titleLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
The label's text is then assigned in cellForRowAt method but in each case returns same hight even though the text is sometimes 4 lines and cell's hight should be stretched.
What is there that I'm missing in the above code? Thanks
The cell's content needs to have autolayout constraints setup in such way that there is constraint connection from the top to the bottom of the cell, for the automatic dimensions to work.
private func setupView() {
addSubview(titleLabel)
titleLabel.snp.makeConstraints { maker in
maker.edges.equalToSuperview()
//or add .top, .left, .right, .bottom constraints individually,
//if you need to add .offset() to each of the sides
}
}
You should give the label top , bottom , leading and trailing constraints to make the height dynamic
I have a plain textView and text inside it. With boundingRect i determine size based on text property of UITextView. Then i apply it to the size of my cell of collectionView. But text do not lay completely inside textView. but if i add to the width of the retrieved size 10 points everything works ok
Maybe i did not take something into account when was applying size to the cell?
Here i created my textView inside cell Class:
class experimentalCell: BaseCellInAppStoreFolder {
lazy var familySharingView: UITextView = {
let tv = UITextView()
tv.text = "Family Sharing"
tv.textAlignment = .right
tv.backgroundColor = UIColor.green
tv.textContainer.maximumNumberOfLines = 1
tv.font = UIFont.systemFont(ofSize: 11)
let options = NSStringDrawingOptions.usesLineFragmentOrigin
let dummySize = CGSize(width: 1000, height: self.frame.height - 16)
let rect = tv.text?.boundingRect(with: dummySize, options: options, context: nil)
tv.textContainerInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
return tv
}()
override func setupViews() {
super.setupViews()
addSubview(textView)
addConstraintsWithFormat("H:|[v0]|", views: textView)
addConstraintsWithFormat("V:|[v0]|", views: textView)
}
}
Then I applied size obtained form rect property to size of my cell.
I'd like to create a centred list of tags, along the lines of this, but centred:
Image source: http://blog.gqueues.com/2013/07/android-vs-ios-comparing-development.html
I thought a nice way to do this would be to extend UIStackView, so that I can have an 'addData' function to do go through the process of creating a view and adding it to the stack. Here's the code so far:
import Foundation
import UIKit
class DataView: UIStackView {
func addData(_ data: String) {
let view = UIView()
let label = UILabel()
view.backgroundColor = UIColor.blue
label.textColor = UIColor.white
label.text = data
label.sizeToFit()
view.frame.size = label.frame.size
view.addSubview(label)
self.addArrangedSubview(view)
}
}
Unfortunately, that doesn't quite look right. The only way I could get the stack view to display the items was to set constraints on either the width, or left and right to force the width. I also needed to set the `Stack View settings to Alignment:Fill and Distribution:Fill Equally in order to see any of them.
My
Does anybody have any idea how I can get a better result here?
In the end, the key to solving this was to specify widths of each of the views and use auto layout to centre the labels. Finally, I needed to calculate the total width, so that with the mode of the UIStackView set to Distribution: Equal Spacing, the items would be spaced out appropriately. Here's my final code:
import Foundation
import UIKit
class DataView: UIStackView {
var widthRequired = CGFloat()
func addData(_ data: String) {
let view = UIView()
let label = UILabel()
label.font = uifont_notification
view.backgroundColor = uicolor_verylight
label.textColor = uicolor_dark
label.text = data
label.textAlignment = .center
label.sizeToFit()
view.addSubview(label)
widthRequired = label.frame.width + widthRequired + 24
label.translatesAutoresizingMaskIntoConstraints = false
view.translatesAutoresizingMaskIntoConstraints = false
let labelView = ["label" : label]
let formatString = "H:|-[label]-|"
let formatStringV = "V:|-[label]-|"
let outerView = ["view" : view]
let formatStringW = "[view(==\(label.frame.width + 20))]"
let constraintH = NSLayoutConstraint.constraints(withVisualFormat: formatString, options:.alignAllCenterX , metrics: nil, views: labelView)
let constraintV = NSLayoutConstraint.constraints(withVisualFormat: formatStringV, options:.alignAllCenterX , metrics: nil, views: labelView)
let constraintW = NSLayoutConstraint.constraints(withVisualFormat: formatStringW, options:.alignAllCenterX , metrics: nil, views: outerView)
NSLayoutConstraint.activate(constraintH)
NSLayoutConstraint.activate(constraintV)
NSLayoutConstraint.activate(constraintW)
view.layer.cornerRadius = 4
self.addArrangedSubview(view)
label.setNeedsLayout()
}
}
Used like this, within a collectionView cellForItemAt function
cell.itemData.addData("One")
cell.itemData.addData("Two")
cell.itemData.addData("Three")
cell.itemDataWidth.constant = cell.itemData.widthRequired // The width of the `UIStackView` constraint as a #IBOutlet in the custom cell
Hope that helps somebody. Here's what the final image looks like when in use:
I want to give a chat aspect to a table view of messages in my iPhone app.
In order to perform a quality render I add two subviews: the text and the "container" which is just a view with background color.
Even if it works the first time, when I scroll, it becomes really messy because it keeps adding lots of subviews.
Here you can see it when clean
And then when it has become messy
Here is the function to handle the transform, it's called when scrolling.
func configChatCell(cell: UITableViewCell, text: String, color:UIColor)
{
cell.textLabel?.numberOfLines = 0
cell.textLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping
cell.textLabel?.textColor = UIColor.whiteColor()
let fixedWidth = cell.bounds.width - 150
let textView: UITextView = UITextView(frame: CGRect(x: 0, y: 0, width: 10, height: CGFloat.max))
textView.text = text
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = textView.frame
newFrame.size = CGSize(width: min(newSize.width, fixedWidth), height: newSize.height)
textView.sizeThatFits(newFrame.size)
textView.frame = newFrame
textView.backgroundColor = UIColor.clearColor()
self.rowHeight = textView.frame.height+20
let view = UIView()
view.backgroundColor = color
print(textView.frame.height+10)
view.frame = CGRect(x: 0, y: 5, width: textView.frame.width+50, height: textView.frame.height+10)
view.layer.cornerRadius = 5
cell.backgroundColor = UIColor.clearColor()
cell.contentView.addSubview(view)
cell.contentView.addSubview(textView)
cell.contentView.sendSubviewToBack(view)
}
If I remove the subviews each time I scroll, nothing appears on screen.
Can somebody help me to find a solution? Or is there any other way to do this?
Thanks in advance!
I quickly wrote up something for this.
It starts with the ChatCell
class ChatCell: UITableViewCell {
var messageLabel: UILabel? {
didSet {
messageLabel?.text = message
}
}
var message: String? {
didSet {
messageLabel?.text = message
}
}
class func messageCell(withText text: String, leading: Bool = true) -> ChatCell {
let cell = ChatCell()
cell.message = text
// Make the container
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(container)
container.topAnchor.constraintEqualToAnchor(cell.contentView.topAnchor, constant: 8).active = true
container.bottomAnchor.constraintEqualToAnchor(cell.contentView.bottomAnchor, constant: -8).active = true
if leading {
container.leadingAnchor.constraintEqualToAnchor(cell.contentView.leadingAnchor, constant: leading ? 8 : 8*8).active = true
container.trailingAnchor.constraintLessThanOrEqualToAnchor(cell.contentView.trailingAnchor, constant: leading ? -8*8 : -8).active = true
} else {
container.leadingAnchor.constraintGreaterThanOrEqualToAnchor(cell.contentView.leadingAnchor, constant: leading ? 8 : 8*8).active = true
container.trailingAnchor.constraintEqualToAnchor(cell.contentView.trailingAnchor, constant: leading ? -8*8 : -8).active = true
}
// Make the messageLabel.
let messageLabel = UILabel()
messageLabel.numberOfLines = 0
messageLabel.textColor = UIColor.whiteColor()
messageLabel.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(messageLabel)
// Add constraints.
messageLabel.topAnchor.constraintEqualToAnchor(container.topAnchor, constant: 8).active = true
messageLabel.bottomAnchor.constraintEqualToAnchor(container.bottomAnchor, constant: -8).active = true
messageLabel.leadingAnchor.constraintEqualToAnchor(container.leadingAnchor, constant: 8).active = true
messageLabel.trailingAnchor.constraintEqualToAnchor(container.trailingAnchor, constant: -8).active = true
cell.messageLabel = messageLabel
container.backgroundColor = UIColor(red:0.19, green:0.70, blue:1.00, alpha:1.00)
container.layer.cornerRadius = 12.0
return cell
}
}
The cell also includes support for leading and trailing messages, for back and forth conversation. Perhaps make an array of tuples like this:
let messages: [(message: String, leading: Bool)] = [("Hello", true), ("My name is John Doe and this works quite well", false), ("I would agree", true)]
Then in your tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell you could do this:
let cell = ChatCell.messageCell(withText: messages[indexPath.row].message, leading: messages[indexPath.row].leading)
return cell
Let me know if this works for you. I tested it in a Playground and it works as expected
Assuming that your configureChatCell is called from tableView:cellForRowAtIndexPath:, then #Paulw11 is right; cells are reused, so you should only make changes that are unique to that row in the table. In your example, the only calls that you should be making in your method are textView.text = text and the ones to resize the textView to fit. Everything else should go in a dynamic cell prototype in the storyboard or, if you want to do everything in code (which I have a bad feeling you do), then put the rest of the configuration in a UITableViewCell subclass, then register that subclass with your table view.
I write something like this. It's simple but can elucidate solution
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseid", forIndexPath: indexPath)
// Configure the cell...
let contentView = cell.contentView
let subviews = contentView.subviews
for view in subviews {
if 100 == view.tag {
let label = view as! UILabel
label.text = self.datas[indexPath.row]
} else if 200 == view.tag {
view.layer.cornerRadius = 10.0
}
}
return cell
}
the key point is config every thing in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
the view in code with tag 200 is a background view has same frame with label, I use layout constraint in storyboard to make sure its size and position.
I had tried this method and I am using a label as a subview to the table cell the text in the label gets overlapped with the previous cell label content while scrolling
var tableCell: CommentTableViewCell? = tableView.dequeueReusableCellWithIdentifier("CommentCell") as? CommentTableViewCell
tableCell!.selectionStyle = UITableViewCellSelectionStyle.None;
if (tableCell == nil) {
println("table view is working")
tableCell = CommentTableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"CommentCell")
}
}
You have to set property for each cell in cellAtIndexPath. This kind of things mostly happen when you have images, but is reproducible with text as well. Lets say that you have custom cell, then you will have to check every time if that particular row has label, text, image etc.
Why does this kind of behavior happen? Because each of your cells are reused, and if reused cell does contain text that you don't have in a "new" cell, or image, or whatever, that will remain and create you an inconsistent bug.
How do you resolve it?
Try to set empty text when you get the cell. If your cell is custom, set both your label to empty string and default cells title label to empty string. Then retrieve your comment from array and assign it. If you provide some more code I will post you what should be changed.(I need cellAtIndexPath method).
So this is your code:
var tableCell: CommentTableViewCell? = tableView.dequeueReusableCellWithIdentifier("CommentCell") as? CommentTableViewCell
let label:UILabel = UILabel(frame: CGRectMake(60, 25, 255, 50))
label.attributedText = text1 label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping;
let font = UIFont(name: "Helvetica", size: 13.0)
label.font = font label.sizeToFit();
tableCell!.addSubview(label)
As I said in comment, your problem is adding label for each reused cell which will create overlapping. To fix this try:
var tableCell: CommentTableViewCell? = tableView.dequeueReusableCellWithIdentifier("CommentCell") as? CommentTableViewCell
if(!tableCell)
{
let label:UILabel = UILabel(frame: CGRectMake(60, 25, 255, 50))
label.attributedText = text1 label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping;
let font = UIFont(name: "Helvetica", size: 13.0)
label.font = font label.sizeToFit();
label.tag = 1; //This is tag added
// add text
tableCell!.addSubview(label)
}
else
{
let label : UILabel = (UILabel *)cell.viewWithTag(1) as UILabel?
// add text
}