UILabel wrong word wrap in iOS 11
Asked Answered
A

8

67

I have problem with application using XIBs without autolayout. I don't know if this is important information.

I have UILabel with 2 lines using word wrap. In iOS 10 word wrap was working correctly, and first line contained one word + special character, for example ampersand. Example:

UiLabel on ios 10

Then on iOS 11 word wrap is working somehow wrong and puts ampresand to the second line:

UiLabel on ios 11

This is problematic as longer words, that normally fitted on second line now are not being shown correctly. Any idea what has changed? I know about safeArea but it doesn't look like reason. Any ideas how to move that ampersand to the top where is plenty of space for it?

Rest of the settings: size inspector

Ait answered 13/9, 2017 at 14:21 Comment(6)
I just hit the same issue and can only guess: It seems the text wrapping algorithm has changed in iOS 11 so the result is more balanced/looks more boxed.Tamis
Looks like we have the same issue too. This seems like a bug, because UILabel has historically been used in situations where we want to simulate typical word processor (or web browser) word wrap, not figuring out the smallest box that can hold all the text.Havildar
I'm seeing the same issue (with ampersands). If I build the app with Xcode 8 (against iOS 10) then run that build on iOS 11 I see the new word-wrapping behaviour. So, even when notionally providing backward-compatibility with an iOS 10 app, iOS 11 is word-wrapping differently.Bilbrey
I believe it’s a bug to force this behavior and finally got around to filing 36021540 with Apple. If anyone else files, you can reference this.Havildar
This issue is coming from ios 11Xiphoid
https://mcmap.net/q/23841/-label-line-break-not-working-properly-in-xcodeOverthrust
D
86

This is a change by Apple to prevent widowed lines. From a design perspective, it is preferred to avoid having a single word on a line of text. UILabel now breaks the line in a way that the second line of text always has at least 2 words on it.

See the answer below for an option to disable it.

enter image description here

Also here's a good article about "widowed" and "orphaned" text.

Darondarooge answered 23/10, 2017 at 19:35 Comment(6)
After testing I can conclude that iOS 11 does pull an additional word down with an orphaned word.Biogeochemistry
I can also verify this behavior of UILabels after changing the text in my app, it correctly wrapped the words.Burberry
Oh, this is interesting.Illogic
But only with a two-line label? I'm seeing it allow a single-word last line when there are more than two lines total.Poised
What I can't understand though is with is the width of the label left bigger than the actual displayed text. This way it's impossible to align, for example, an emoji next to a 2 lines label.Feria
According to Wikipedia that article has got widow and orphan the wrong way roundSaccharometer
A
36

Since iOS 14 you can use lineBreakStrategy property of UILabel instance to control this behavior.

Available values are:

NSParagraphStyle.LineBreakStrategy() // none
NSParagraphStyle.LineBreakStrategy.pushOut
NSParagraphStyle.LineBreakStrategy.hangulWordPriority
NSParagraphStyle.LineBreakStrategy.standard

To disable this behavior using Swift:

if #available(iOS 14.0, *) {
    label.lineBreakStrategy = []
}

// Alternatives
// label.lineBreakStrategy = NSParagraphStyle.LineBreakStrategy()
// label.lineBreakStrategy = .init(rawValue: 0)
// label.lineBreakStrategy = .init()

To make it work on lower iOS versions, you can use NSAttributedString:

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakStrategy = []

let attributedString = NSAttributedString(string: "Your text here", attributes: [
    .paragraphStyle: paragraphStyle
])

let label = UILabel()
label.attributedText = attributedString

Objective-C:

if (@available(iOS 14.0, *)) {
    label.lineBreakStrategy = NSLineBreakStrategyNone;
}
Abomination answered 26/11, 2020 at 16:31 Comment(13)
Thank you so much for posting this! (And odd that the Objective-C version is much more readable!)Havildar
@DavidDunham I know right? Why hasn't swift got LineBreakStrategy.none?? In some ways Swift can be so unintuitive, and it's marketed at young kids to get them into coding...Severally
@Severally It's an option set, therefore you can use [].Chrotoem
It works below iOS 14.0, since NSParagraphStyle.LineBreakStrategy is available since iOS 9.0. And a more swifty way: extension NSParagraphStyle.LineBreakStrategy { static var none: Self = [] } and use it like: lineBreakStrategy = .noneStorm
This should be the accepted answer.Aquifer
@jasond it doesn't work below iOS 14, I just tried and its crashing my app on iOS 12.5.5Nordine
@Abomination what to do for devices having iOS below than 14?Nordine
@RajaSaad what error did xcode say? for me it simply works. can you provide me steps to reproduce your error? I would like to solve it "once and for all" :DStorm
@jasond it simply crashes my app on iOS 12.5.5, and here is the error:Fatal Exception: NSInvalidArgumentException -[UILabel setLineBreakStrategy:]: unrecognized selector sent to instance 0x1072f3560Nordine
@RajaSaad sorry, you're right. It's confusing: the NSParagraphStyle.LineBreakStrategy is available at least from iOS 9, and the UILabel's lineBreakStrategy property doesn't have an available() mark, so I wrongly assumed that it's available on iOS 12 too - however in the online docs it says that it's available only from 14. But on 13 it works. We can check the selector that you wrote too (setLineBreakStrategy:), and below 13 it says that it cannot respond to it. Interesting, but if you work according to the online docs, you are on a good way. Currently I don't have any ideas for your problem...Storm
@RajaSaad you can use it with NSAttributedString even below iOS 13, I updated this post to include the example on how to do this.Abomination
Do not set it to None (or [] in Swift). That will remove other values, such as the Korean hangul setting, which you probably want (and any other that Apple adds by default in the future). I would do label.lineBreakStrategy &= ~NSLineBreakStrategyPushOut; in ObjC, and label.lineBreakStrategy.remove(.pushOut) in Swift.Stagecraft
Very good point @CarlLindberg!Abomination
S
24

Launching the app with the arguments -NSAllowsDefaultLineBreakStrategy NO (an undocumented defaults setting) seems to force back to the old behavior. Alternatively, you can set NSAllowsDefaultLineBreakStrategy to NO in NSUserDefaults at startup (Apple registers a default of YES for that value when UILabel or the string drawing code is initialized, it appears, so you would need to register an overriding value after that, or insert it into the NSArgumentDomain, or just set the default persistently).

Apple may consider that private API and reject apps that use it; I'm not sure. I have not tried this in a shipping app. However, it does work in quick testing -- saw the setting in NSUserDefaults and found changing it altered the behavior.

Stagecraft answered 2/3, 2018 at 5:47 Comment(4)
works great! added it to my UILabel category for init. :-)Chinchy
Actually, just setting NSAllowsDefaultLineBreakStrategy to false at app launch worked perfectly!!Chinchy
Yes, that will override the registerDefaults: call that Apple does. That will also persistently set it as a user default. You can also put it into a dictionary (best to start with the current one) and call [defaults setVolatileDomain:dict forName:NSArgumentDomain], so it's equivalent of a runtime argument -- that is not persistent on disk, but still overrides Apple. It might even work if you just temporarily set that during a UILabel drawRect override, to make it only used in some situations.Stagecraft
This does indeed work with UserDefaults.standard.set(false, forKey: "NSAllowsDefaultLineBreakStrategy"). Did anyone use this in a shipping app? Did Apple reject it or not?Arium
H
14

This is not really an answer, but I want to add an illustration of how it is a general problem, not at all related to ampersands.

two UILabels

Both of these UILabels have identical width constraints, and the text is almost identical. But the second has the word wrap I would expect. The first is incorrect, the "about" can clearly stay on the first line.

Havildar answered 20/10, 2017 at 18:48 Comment(1)
Good example. This type of word wrapping is especially annoying when trying to place the text in some sort of chat bubble or other "container" view since, as you said, the width of the label is said to be the same in both cases, when in reality, it should be far narrower in the first case. Leads to a lot of blank space in said container views.Episiotomy
S
3

A bit of a hack but you can add some zero width spaces to the end of the string to restore the old behaviour, without affecting the layout of the string otherwise that you'd get from normal spaces:

let zeroWidthSpace: Character = "\u{200B}"
let spacingForWordWrapping = String(repeating: zeroWidthSpace, count: 6)
label.text = "oneText & two" + spacingForWordWrapping
Saccharometer answered 18/9, 2020 at 14:17 Comment(2)
Thanks! @Saccharometer The only solution that helps me till now. clean and simple.Tinner
yes, this one (simple) solution worked for me. And the beauty is that it also works with SwiftUIImprobable
B
1

It seems that replacing the space before the ampersand with a non-breaking space (U+00A0) keeps the ampersand on the same line. Depending on how you are generating the text for the label, this might not be easy to automate (maybe you really do need the ampersand to be on the second line in some cases).

Bilbrey answered 16/10, 2017 at 8:35 Comment(0)
S
1

An option may be to use a UITextView instead -- that does not seem to have this behavior. If you set the NSTextContainer.lineFragmentPadding to 0, the textContainerInset to UIEdgeInsetsZero, and turn off all scrolling (scrollEnabled, bounces, scroll indicators, etc.) it will display similarly to a UILabel, though not with as much constraint flexibility. It's not a drop-in replacement, but in some situations it's acceptable.

Stagecraft answered 2/3, 2018 at 5:57 Comment(4)
Yes but how can we then remove text highlighting or selection in that?Journalese
Turn off the selectable and editable properties as well. Turning off userInteractionEnabled wouldn't hurt either.Stagecraft
Yes in this case I forgot that..yes it could be done..But if you want links or hypertexts like tags hashtags just like twitter and instagram.. the highlight won't be removed...Journalese
You can't click links in UILabel anyways (even if they display blue), so I'm not sure what you'd be missing from what a UILabel would be. With UITextView, you could probably leave user interaction on (even if selectable is false) and get the link taps in the delegate methods.Stagecraft
L
-1

As a simple (hacky) workaround, you can often get the correct behaviour with a UILabel by adding spaces at the end of your text. Using your example:

Wraps the new (undesired) way:
"oneText & two."

Wraps the old way:
"oneText & two. " (note the 2 extra spaces at the end of the string)

The obvious downside is if those extra spaces get forced to a new line by themselves, but for something simple like a title it's often enough.

Locally answered 28/10, 2019 at 21:56 Comment(1)
In my case, I needed to add 6 extra spaces. Anything less did not work...Antipode

© 2022 - 2024 — McMap. All rights reserved.