Truncate text in label - swift

I am trying to use "Truncate Middle" on a single line label. I checked "Uses Single Line Mode", set Layout to "Truncates" and Line Break to "Truncate Middle" and I've set the Preferred width of the text field to "First Runtime Layout Width" and Content Hugging Priority to 250.
Here's my layout with the <Title> label on which I am trying to enable the truncation:
In my ViewController:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
titleLabel.stringValue = "This is a very very long title that should be truncated because it would enlarge the window unnecessary!"
}
It does not work, the window is enlarged to show the complete title and the user can't reduce its width.
Strangely, if I change the layout and line break from "Truncates"/"Truncate Middle" to "Wraps"/"Character Wrap", it works fine, but without the middle truncation.
I am expecting truncate to work like this:
This is a very very long text...rge the window unnecessary!
Maybe I'm missing something?
EDIT: Here's the sample project to test the issue: https://nofile.io/f/9s9OJXHaaw1/TestTruncate.zip

If you use Auto Layout for your NSTextView and surrounding NSView you should set your Content Compression Resistance Priority of the NSTextView less than NSLayoutPriorityWindowSizeStayPut (e.g. 499). Otherwise your NSTextView will not truncate its content. (Reference)
I was able to achieve this by setting Content Compression Resistance Priority of Horizontal from 500 to 499.
You can do so by selecting your label -> Size Inspector -> Content Compression Resistance Priority
Giving me the following output:

This is something very simple. Just add this line in override func viewDidLoad()
yourLabel.lineBreakMode = .byTruncatingMiddle
Or you can also set it from Interface Builder. Select your label and set Line Break as Truncate Middle.

For truncate middle, you have to set the alignment to centre as well.

Related

Swift: Auto Layout - Columns of Text - removing trailing and leading warnings

I have two columns of text. The left is anchored top, left and bottom and the righthand side conversely. This still generates leading and trailing warnings. How do I connect the two columns' rows to tell Auto Layout to just expand the space in between?
While YOU know exactly what text you're going to put into those labels, Storyboard / Interface Builder (IB) has no idea.
So this looks great to you:
But... what happens if the "Date" text changes to "When do you want to get started?":
Because we haven't given a constraint between the two labels, they overlap.
So, let's do the same thing on both "rows" but, add a Trailing-to-Leading constraint of 8 between the labels:
We've prevented the overlap but now we see a new problem (that IB will warn you about)... Which label should get compressed? IB (and auto-layout at run-time) will make its own decision, which may not be what you want, and which may be inconsistent between similar layouts.
To fix that, we give a higher Content Compression Resistance Priority to the label we do not want compressed:
And here is the result - top "row" has the left-hand label at the default of 750, and the right-hand label at 751, and the bottom "row" has the left-hand label at 751, and the right-hand label at the default of 750:
It looks the same as "C" but we no longer have errors/warnings from IB.
So, even if you know the text in your two columns will never be enough to overlap, IB is going to encourage you to provide enough constraints (and priority settings) to make sure you get exactly what you want.

Change height of one of multiple fields in stack view

In my code I have a stack view that initially has 1 element PhoneNumberField. Another PhoneNumberFieldscan be added dynamically in the runtime:
#IBAction func addAlternatePhoneNumberAction(_ sender: UIButton) {
let alternateNumberView = PhoneNumberField()
...
phoneNumberStackView.addArrangedSubview(alternateNumberView)
}
This is what xib for PhoneNumberFieldlooks like:
The problem is that I would like to be able to dynamically hide the 'Name for other phone' field based on the content of 'Mobile combobox'. When I set the 'isHidden' parameter everything works as expected, the only problem is that the PhoneNumberField height stays the same. I would like it to shrink when the 'Name for other phone' field is hidden.
I tried doing it using the outlet for height constant for otherNumberNameField in the PhoneNumberField.swift file but the problem is that in that case all of the PhoneNumberFields in the stack view have the size of the first field.
What would be the correct solution for this?
edit: In addition to the answer below: I had to set the distribution for the phoneNumberStackView to equal spacing. Worked like a charm.
First, create StackView.
Don't set its height constraint, just set top, leading, trailing and bottom constraints.
Bottom constraint set equal to Error label top constraint.
Then set its distribution to Fill Equally.
Now put first two Views into one view and put this view together with OtherNumberField view to this StackView.
So now your hierarchy should look like this:
Now when you hide one view from StackView, StackView will be smaller because you didn't set its height.

NSTextView in NSOutlineView with IntrinsicContentSize setting wrong height

I have an outlineView in which I am putting NSTextViews that resize when edited (think outliner app). I have most of this working, but some behaviour is inconsistent.
On my NSOutlineView I set:
outlineView?.usesAutomaticRowHeights = true
For my cell-views I subclass NSTextView. I set the following auto layout bits:
self.translatesAutoresizingMaskIntoConstraints = false
setContentHuggingPriority(NSLayoutConstraint.Priority.defaultHigh, for: NSLayoutConstraint.Orientation.vertical)
And I override the intrinsic content size calculation on the NSTextView:
override var intrinsicContentSize: NSSize {
guard let manager = textContainer?.layoutManager else {
return .zero
}
print("\(manager.usedRect(for: textContainer!).size) \(string)")
return manager.usedRect(for: textContainer!).size
}
(I was calling ensureLayout on the layoutManager in the code above but it adds nothing)
intrinsicContentSize is called twice per text view when they are added to the outliner. The first time the size returned is correct, but on the second call some of the text wraps unnecessarily. A printout of the two passes on intrinsicContentSize for 4 text views are shown below. The column width is 281, so none of these strings should wrap. The first pass they all fit to one line (14 high), on the second pass, the last two strings wrap, which is strange because they are not the longest strings:
(178.744140625, 14.0) New pointwddwek kelekwelek...
(100.720703125, 14.0) Related Subjects
(119.400390625, 14.0) Related Publications
(87.150390625, 14.0) Related Terms
(178.744140625, 14.0) New pointwddwek kelekwelek...
(100.720703125, 14.0) Related Subjects
(74.705078125, 28.0) Related Publications
(54.484375, 28.0) Related Terms
It is consistently the same strings that result in the same behaviour. E.g. the string "Related Subjects" never wraps, the string "Related Terms" always wraps.
When the views are presented, the text is NOT actually wrapped, even thought the usedRect value implies that it would be. The text is shown correctly, but the row view in the outliner is too high because it thinks it has two lines of text.
Any pointers where I might be missing something? Does 'ensureLayout' somehow refer to it's previous calculation and then have rounding issues when fitting the same string into its last-calculated width?
OK, the key here was that the text was presented correctly but the size of the view was wrong.
I created a delagate for the NSTextView's layoutManager. The text was being set out 3 times after the NSTextView was added to the NSOutlineView (which seems to be terribly inefficient!). intrinsicContentSize was only being called on the NSTextView after the first two text layouts.
Calling invalidateIntrinsicContentSize from within layoutManager: didCompleteLayoutFor... fixed everything up.
I still don't understand what is happening here though, and why all these methods are being called so many times when stuff is presented. I guess it is to do with the complexity of auto layout and things pushing against each other.
I also don't understand why only some of the calculations were incorrect during the process.
Please comment here if anyone can shine some light on this!

NSTextField label: dynamically change content and grow width to fit

If I drag a Label (of class NSTextField) into a view in Interface Builder, and then attempt to set its stringValue dynamically like so:
self.testingLabel.stringValue = "Labels are really really really long"
Then most of the label will be cut off.
How can I make the label grow to fit its content without wrapping/scrolling?
I have tried using this custom class but it doesn't work.
class AutogrowTextField: NSTextField {
override var intrinsicContentSize: NSSize {
get {
var frame = self.frame;
frame.size.width = CGFloat.max;
// Calculate new height within the frame
// with practically infinite height.
let width = self.cell!.cellSizeForBounds(frame).width
Swift.print(width)
NSBeep()
Swift.print(self.frame.width)
return NSMakeSize(width, frame.size.height)
}
}
// you need to invalidate the layout on text change, else it wouldn't grow by changing the text
override func textDidChange(notification: NSNotification) {
super.textDidChange(notification)
self.invalidateIntrinsicContentSize()
}
}
Essentially I just want the default behaviour of an HTML document like this:
<p id="dynamic">Hi</p>
<script>
document.getElementByID("dynamic").innerHtml = "Something really really really long"
</script>
This is extremely painful for me as while I am new to Mac OS X and Cocoa, I am not a 'beginner' programmer.
In case it helps here is the contents of the attributes inspector. (it should just be all default values)
Yes, auto layout is enabled
Are you sure your NIB or storyboard is configured to use auto layout? That would be a checkbox on the File inspector.
The reason I ask is that the Attributes inspector would have a "Preferred Width _ First Runtime Layout Width" checkbox, immediately below the "Uses Single Line Mode" checkbox. It doesn't, which makes me think that auto layout is not enabled.
If you're not using auto layout, then you are responsible for either setting the label's frame size or, more simply, telling it to size itself to fit its content by calling sizeToFit().
If you're using auto layout, then the standard NSTextField already computes its intrinsicContentSize based on its string content. The problem is almost certainly with whatever other constraints you may have set on the view. You need to make sure that the auto layout system is free to adjust the label's width. So, you must not set an explicit width constraint. Or, if you have, say, both a leading and trailing spacing constraint, you need to make sure the label's compression-resistance priority is high enough to push the other related views out or compress them. Etc.

Center Multi-Line Text on UIButton using IB

How do you center text line-by-line in a UIButton using Interface Builder? I am scouring the options and just don't see it. Here's the button:
You can't set the text to be centered in your nib. But you can change the alignment in your code:
- (void)viewDidLoad {
[super viewDidLoad];
self.myButton.titleLabel.textAlignment = UITextAlignmentCenter;
}
I know this is an old question, but I came across it in my own attempt to center the multi-line text of a UIButton in IB. What I found is that by default, when "title" is set to "plain" and "line break" is set to "word wrap" the longest line of the title text is centered and the other lines are left justified to this line (similar to the OP's screen capture).
In order to have all the lines centered properly, "title" needs to be changed to "attributed." This provides many more options to customize the appearance of the title text. Center each of the lines of text (you can now actually change the alignment for each line individually). Also be sure to set "line breaking" to "word wrap" under "more..." above the text. There seems to be a bug with how this line breaking option behaves, at least in Xcode 4.5 at this time, because the text on the button in IB will look incorrect, truncating everything except the first line. It seems the "word wrap" and truncate options are interpreted backwards in IB, but if you run the app it behaves correctly in the simulator.
Actually you can do it in interface builder.
Just set Title to "Attributed" and then choose center alignment.
#from comments : To wrap you need to set Line Break to Character Wrap or Word Wrap.
P.S : This might not render in xcode. But, it will work at runtime.
You can set the center multiline text in UIButton through storyboard.
This is how you make the text have two or more lines.
Set the below key Path at
Identity Inspector --> User defined runtime attributes --> add new key value pair with below
titleLabel.textAlignment - NSNumber - 1
and
titleLabel.numberOfLines - NSNumber - 5 - or use "0" meaning "any number"
It will look like this:
Note that (2016) unfortunately it does not actually show the two or more lines of text in Storyboard (you see only the first one), but it works perfectly when you run in simulator or device.
For IB set Title to "Attributed" and select center alignment (like Alexander Danilov suggested)
But if you want to do it in code using Swift 4:
// center button text
yourButton.titleLabel?.textAlignment = .center
// enable multiline if needed
yourButton.titleLabel?.numberOfLines = 0
Not all options are done using Interface Builder therefore you must do some of them by coding, and usually we do them inside the function viewDidLoad.
To center your text inside a button by code you can use the following:
button1.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
You can use the same technique to align the text to any direction, for example to the left:
button1.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
But this will take the text too much to the left and you might want to have some space before it keeping the alignment to the left, so you add an inset after the aligning code as follows:
button1.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
In this case we are pushing the text from the Y axis by 10 points. I say here points not pixels because as you know Apple uses the points technique to measure distances to be able to adapt easily between normal display and retina display (where retina is 2 times the normal one).
I haven't tried it out yet, but I think a way to do it might be create a CGRect on top of your button, then use it as a frame, create a label, and then you can play with the label, set the textAlignment property to be UITextAlignmentCenter, and set the background color to be clear.
This works with uitableview but I don't know whether that will work for button. Hope this helps.