UILabel wrong word wrap in iOS 11

IosIphoneUilabelIos11

Ios Problem Overview


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

Ios Solutions


Solution 1 - Ios

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.

Solution 2 - Ios

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.

Solution 3 - Ios

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;
}

Solution 4 - Ios

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.

Solution 5 - Ios

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).

Solution 6 - Ios

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.

Solution 7 - Ios

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

Solution 8 - Ios

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionstudentbiView Question on Stackoverflow
Solution 1 - IosbrynbodayleView Answer on Stackoverflow
Solution 2 - IosCarl LindbergView Answer on Stackoverflow
Solution 3 - IosKarlisView Answer on Stackoverflow
Solution 4 - IosDavid DunhamView Answer on Stackoverflow
Solution 5 - IosGeoff HackworthView Answer on Stackoverflow
Solution 6 - IosCarl LindbergView Answer on Stackoverflow
Solution 7 - IosJonathan.View Answer on Stackoverflow
Solution 8 - IosPatrickView Answer on Stackoverflow