NSAttributedText not working for UILabel in Swift - swift

I'm using Swift 3 on an iOS 10.3 iPhone 6s.
I have an extension method on NSAttributedString:
extension NSAttributedString {
class func attributedString(title: String, titleFont: UIFont, body: String, bodyFont: UIFont) -> NSAttributedString {
let titleAttributes = [NSFontAttributeName : titleFont]
let bodyAttributes = [NSFontAttributeName : bodyFont]
let title = NSAttributedString(string: "\(title): ", attributes: titleAttributes)
let body = NSAttributedString(string: "\(body)", attributes: bodyAttributes)
let text = NSMutableAttributedString()
text.append(title)
text.append(body)
return text
}
}
And I have this UITableViewCell configure method that gets called when setting up the cell in cellForRowAtIndexPath:
class MyCustomCell: UITableViewCell {
...
func configure() {
...
myLabel.attributedText = NSAttributedString.attributedString(title: t, titleFont: tFont, body: b, bodyFont: bFont)
}
}
Somehow the label is blank on the screen.
The params are not optional (in other words, t and b are not and cannot be nil).
If I tap the cell, it opens a screen and then when I come back, the label is showing the text correctly. I've tried adding things like myLabel.setNeedsDisplay(), setNeedsDisplay(), setNeedsLayout(), etc. to the end of the configure method, but nothing's working.
What am I doing wrong?
Other attributed string issues have been because people used things like regular NSFontAttributeName with the documentAttributes: init method. That's not what I'm doing here.
UPDATE:
If I pause with the debugger to inspect the UI, here's what the label shows:
<UILabel: 0x1041734b0; frame = (20 0; 297 0); text = 'Title: body'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x174298290>>
I don't understand why it's got a height of 0. The label's vertical content hugging and content compression resistance priorities are set to 1000 in the storyboard, which normally works great when I use a UILabel's text property. Maybe the vertical hugging and compression resistance priorities don't work correctly with attributed text?
UPDATE 2:
I just changed the constraints by setting the label's vertical content hugging and content compression resistance priorities back to default and giving the label a height constraint of 20. Now the label shows correctly!
I don't like this solution because I want the label to automatically have a height of 0 if there is no text and, if there is text, I want the label's height to match the font size (which can vary depending on user preference's in my app). In other words, I don't want to have to manage the label's height constraint.
Update 3:
Something I didn't show in my configure method...at the beginning I have a guard statement where I clear the label's attributed text if there's no title or body to show. My goal is for the label to hide. However, if there's a title and body, then I proceed to the line I showed in the configure method above.
When I remove this line in the guard statement, the label is visible with a valid height. The behavior is the same when I use the label's text property, which I didn't realize.
I tried using setNeedsLayout() and layoutIfNeeded() to reload the label so it would set its size correctly when I give it text, but it's not working.
Summary
I want to be able to set the vertical content hugging and content compression resistance priorities to 1000 from the storyboard so that the label's height depends on its content. And when I set the label's attributedText to nil, or "", or "Title: body", I want it to automatically set its height correctly. Is this possible? Am I misunderstanding the content priorities?

I see you got it solved, but thought I'd put this here for anyone else stumbling on this.
Remember when updating the UI to always do it on the main thread. When you reload the cell in cellForRowAtIndexPath I believe your code is being called on the main thread.
When you call it elsewhere, it's a good idea to do it like so:
dispatchQueue.main.async {
configure()
}

So it turns out that my problem was that I was also calling configure from the view controller outside of the context of the cellForRowAtIndexPath: method, and it wasn't reloading the cell's height (even though the label's height had changed since I had set text on it).
So, I needed to add code to reload the cell in the table view:
NSIndexPath *indexPath = ...; // Get index path for the cell I want to update.
VCInboxCell *cell = (MyCustomCell *) [self.table cellForRowAtIndexPath:indexPath];
if (cell) {
[cell configure];
[self.conversationsTable reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
In other words, I was setting up the cell with configure in cellForRowAtIndexPath:, but at that point the label's text was nil (because the model's data for the label wasn't set yet). Then, when the model's data changed for the label, I was calling configure again, but not updating the row, so the label was being squashed due to constraints in the cell, even though it wanted to have a height of 19.5 according to its intrinsicContentSize (based on its text value).
Reloading the cell updates the height and all is well.

Related

NSGridView custom view intrinsic size

I'm building a simple NSGridView, and want to have a custom NSView as each element of the grid. Eventually, each NSView will be a xib based label (NSTextField) centered in the NSView.
The problem I am having is with the intrinsic size of the NSView. I want to define the size of the NSView and have auto layout work based on that. I added this code to the custom view (labelView):
override var intrinsicContentSize: NSSize {
return NSSize(width:100, height:100);
};
And it is indeed called; but apparently ignored. As a test, I have on the same row some other labels, and the height for the row is always set to the largest of the row text heights (including the label in the custom view); but the length is set to the longest of column text fields, ignoring the label in the custom view. And anyway, I want to arbitrarily make the NSView a certain height and length, as I tried (but failed) to do with the intrinsicContentSize.
NSStackview seems to do the right thing; but NSGridView does not.
I can force the width of a particular column with
grid.column(at:0).width = 400;
but want I really want to do is define the size of the NSView, and let autolayout use that as a building block.
This strikes me as a conceptual error on my part, so if someone could explain these NSGridView-autolayout-NSView subtleties, I think many might benefit.
I was having the exact same issue, tried to use custom NSView's inside a NSGridView and couldn't get them to draw correctly. What finally worked for me was setting the following:
let gridSize = 5
let cellSize: CGFloat = 50
gridView.xPlacement = .fill // this was key part of the solution
gridView.yPlacement = .fill
for i in 0 ..< gridSize {
gridView.row(at: i).height = cellSize
gridView.column(at: i).width = cellSize
}
Note that I'm setting the size of each cell with the row height and column width of the NSGridView, and not using the NSView size, but this is the only way I got it working.

how can i set a dynamic height to collection view cell in swift?

HI everyone i'm looking for a working method that allows to dynamically set the height in my collectionViewCell because I tried various other answers without success.
My situation is pretty simple, I have a cell with an image, a UIlabel that displays username and a UILabel which contains some text. It's a common Comment cell for a post. My Screen
I tried this method but it doesn't work precisely because the height is too much compared to what is necessary
private func estimatedFrameForText(text: String) -> CGRect {
let size = CGSize(width: 270, height: 80)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17)], context: nil)
}
so my question is: there is a correct way to implement dynamic size on collection view cell like layout attribute wrap_content for Android?
UPDATE
I solved my problem using constraints and Automatic Estimated Size on Collection View. Setting up all constraints I forced the width of LABEl to be equal to the cell width and for the height I set the top and bottom spacing for the UILabel (with lines 0) to be equal to its content view (the one standard for the UiCollectionViewCell)
IT'S also important to set the size of the collection view cell to automatic
VIEW IMAGE

Automatic height adjustment for static UITableViewCell doesn't work

I have a static UITableView and I want to set the row height for three of the cells dynamically. So in viewDidLoad() I implemented the following code:
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
I also implemented the heightForRowAt method:
(The first two cells of the first section should have a fixed height)
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 0 {
return CGFloat(85)
} else if indexPath.section == 0 && indexPath.row == 1 {
return CGFloat(145)
}
return UITableView.automaticDimension
}
This the result which I'm currently getting:
I changed the lines of the labels to 0, too and the constraints of the labels inside the cells are 0, 12, 0, 12 (top, right, bottom, left).
Does anybody know, why the cell in section 3 doesn't display the data in the right way?
Edit:
(How it looks after the implementation of the suggestion above)
Because sizeToFit() did not work for you, we are going to try something a little more involved.
The cell in section 3 is displaying the data the right way. This is because UILabels don't automatically adjust their height to accommodate the text inside. Here's what you need to do:
1. Create a height constraint for your UILabel In your interface builder, add a constraint for the height of the UILabel in section 3's cell. Connect this height constraint to your view controller's class via an #IBOutlet:
class YourViewController: UIViewController {
#IBOutlet var cellLabel: UILabel!
#IBOutlet var cellLabelHeight: NSLayoutConstraint!
...
}
2. Add String extension that calculates height I am unsure of where/when you are setting the text of the UILabel in question, but I know you are doing this somewhere as you have described it as being "dynamic". Whenever you do set the text of the UILabel in question, you now also need to change the constant of the height constraint that we made in order to accommodate this text. So, we need to be able to calculate the height of the UILabel based on its width and font. We can add an extension to String in order to do this:
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.height)
}
}
3. Set the height constraint's constant based off the UILabel's text The final step is to set the height of the UILabel height constraint we made by using the extension we just created:
cellLabel.text = "DummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyData"
//This will be called immediately after you set the text for the UILabel in question
cellLabelHeight.constant = cellLabel.text.height(withConstrainedWidth: cellLabel.frame.width, font: cellLabel.font)
The cell in section 3 is displaying the data the right way. Unless you tell it otherwise, a UILabel will not automatically adjust to accommodate the text within it.
What I need you to do is select the UILabel in question, then in the attributes inspector, set the Number of Lines to 0.
You also said that this UILabel is dynamic, meaning you are setting it's text somewhere in your code. Immediately after you set this UILabel's text, you are going to want to call myLabel.sizeToFit(). This should adjust the label's height to accommodate the text within.
If this doesn't work, I have another, more involved solution that should work for you.
Please look at the below;
Select your cellLabel and set the Lines value to 0:
Also apple says Self-Sizing
Summary :
lay out your table view cell’s content within the cell’s content view. To define the cell’s height, you need an unbroken chain of constraints and views (with defined heights) to fill the area between the content view’s top edge and its bottom edge. If your views have intrinsic content heights, the system uses those values. If not, you must add the appropriate height constraints, either to the views or to the content view itself.
Change the bottom constraint of the AuthorLabel from equal to Greater than or equal

NSMutableAttributedString not rendering correctly

I am using a SplitViewController; The master is tableView with a series of RightDetail cells whose Attributed Strings are set via a callback from a value setting popover.
The value passed back and set via a method that sets the detailTextLabel of the cell. It does this within a DispatchQueue.main.async as some of updates to detailTextLabel are made via calls from Network.Framework
The strange thing is when the popover makes the changes the change to the attributed string is not presented correctly. The focus has to change to the next table cell before the correct drawing occurs.
So it is only when focus changes to "Flow Rate" that temperature is rendered correctly, as shown blow.
I have stepped through the following code and checked that the correct value is being written to self.buttonCell?.detailTextLabel?.attributedText
You'll also see that I've added setNeedsLayout() and setNeedsFocusUpdate()
func setDetailValueWithUnit( to index: Int) {
/** sets the detailtext to the value as the specified index and appends units.
it does this within the main dispatchQueue as this method is called by
operationResult which is called by the statemachine.
**/
DispatchQueue.main.async {
let font = self.buttonCell?.detailTextLabel?.font
let finalString = NSMutableAttributedString(string: valueString,attributes: [.font:font!])
if let units = self.units() {
finalString.append(units)
self.buttonCell?.detailTextLabel?.attributedText = finalString
//next 2 lines experimental in an attempt to fix the attributed text not
//being consistently redendered correctly
self.buttonCell?.detailTextLabel?.setNeedsLayout()
self.buttonCell?.detailTextLabel?.setNeedsFocusUpdate()
NSLog("setDetailValueWithUnit: %d", index)
return
} else {
assert(false, "No Units found")
}
}
}
The problem is that what you are doing is completely wrong. You should never be touching a cell's text label font, text, or anything else directly like that. That's not how a table view works. You should set the table's data model and update the table from there, thus causing cellForRowAt: to be called for the visible cells. In that way, only cellForRowAt: configures the cell, and all will be well.

Swift - Custom cell width and height for textlabel and detailTextLabel

I wanna manage the long text in textlabel and detailtextlabel for the cells in UITableview because if the textlabel has a long text the detailedtextlabel truncate with "..." and viceversa.
Here's the problem:
So I wanna know if there's a way to manage the space between textlabel and detailtextlabel so there's no truncate and give them more lines. I tried with:
...
if tableData[indexPath.row].clave.count > 6 {
cell?.detailTextLabel?.numberOfLines = 5
cell?.textLabel?.numberOfLines = 5
} else {
cell?.detailTextLabel?.numberOfLines = 1
}
cell?.textLabel?.text = nueva_cadena_string
cell?.detailTextLabel?.text = tableData[indexPath.row].clave
cell?.textLabel?.textColor = UIColor.brown
cell?.textLabel?.font = UIFont.boldSystemFont(ofSize: 15.0)
cell?.textLabel?.lineBreakMode = .byWordWrapping
return cell!;
But it doesn't work very well, it seems to give them just 2 lines, not 5 and I can't change the width for the textlabel and detailtextlabel.
Try making your cell custom as this:
Try setting the tableviews row dimension to automatic
yourTableView.rowHeight = UITableViewAutomaticDimension
yourTableView.estimatedRowHeight = 80.0
try setting both textlabels number of lines either in the storyboard or in code to 0 as then they will all have the amount of lines they need.
You can subclass UITableViewCell and override the layoutSubviews() method. You will need to change the layout of textLabel and detailTextLabel there. Remember that these elements lie inside contentView, not in the cell itself.
But better you add two custom labels on the Storyboard and configure them as you want. It's easier.