How to eliminate superfluous line breaks from NSAttributedString (converted from HTML) in iOS UILabel object?
Asked Answered
M

1

7

I have some table view cells that need to display some simple html from our server. Am converting the html into an NSAttributedString so that it can be displayed in UILabel objects on the cells. But when the table view draws the cells the UILabel objects seem to have some line breaks added to the end of them. Notice extra space here:

enter image description here

(Btw, the pic provides two tableview cells because I tried two different forms of html and in both cases there seem to be inserted line breaks) I thought perhaps it was due to my layout, that maybe the UILabel was being forced into its height by the layout and not free to resize/shrink itself based upon the text it is assigned. But when I supply to the same label a simple NSAttributedString created this way:

 sRet = [[NSAttributedString alloc] initWithString:@"[Dummy text string 111]"];

the (yellow) UILabel indeed shrinks in response like so:

enter image description here

pretty much demonstrating that my layout is allowing the UILabel to freely resize according to the text assigned to it.

Below is the html being assigned to the labels and the resulting NSAttributedString values when I log them to the console. These correspond to what you see in the yellow UILabel objects in the first image above. Here is the html being converted to an attributed string and assigned:

<h2>Student did poorly</h2>

<p><span style="font-family:comic sans ms,cursive;"><span style="font-size:14px;">Student did ok</span></span></p>

And here are the corresponding attributed strings:

Student did poorly
{
    NSColor = "kCGColorSpaceModelRGB 0 0 0 1 ";
    NSFont = "<UICTFont: 0x7fce3f8798e0> font-family: \"TimesNewRomanPS-BoldMT\"; font-weight: bold; font-style: normal; font-size: 18.00pt";
    NSKern = 0;
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 22/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n), DefaultTabInterval 36, Blocks (null), Lists (null), BaseWritingDirection 0, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 2";
    NSStrokeColor = "kCGColorSpaceModelRGB 0 0 0 1 ";
    NSStrokeWidth = 0;
}

Student did ok{
    NSColor = "kCGColorSpaceModelRGB 0 0 0 1 ";
    NSFont = "<UICTFont: 0x7fce3d5a96c0> font-family: \"Snell Roundhand\"; font-weight: normal; font-style: normal; font-size: 14.00pt";
    NSKern = 0;
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 19/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n), DefaultTabInterval 36, Blocks (\n), Lists (\n), BaseWritingDirection 0, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0";
    NSStrokeColor = "kCGColorSpaceModelRGB 0 0 0 1 ";
    NSStrokeWidth = 0;
}
{
    NSColor = "kCGColorSpaceModelRGB 0 0 0 1 ";
    NSFont = "<UICTFont: 0x7fce3d7959e0> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 12.00pt";
    NSKern = 0;
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 15/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n), DefaultTabInterval 36, Blocks (\n), Lists (\n), BaseWritingDirection 0, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0";
    NSStrokeColor = "kCGColorSpaceModelRGB 0 0 0 1 ";
    NSStrokeWidth = 0;
}

In neither the html nor the attributed strings do I see reason for the UILabel objects having that extra vertical space. Fwiw, the pair of UILabel object on each cell (the yellow and the white) are contained within a UIStackView, so perhaps it could somehow be doing the mischief. But only four constraints, trailing, leading, bottom and top, and as mentioned above the label is able to be resized fine so these constraints don't seem to be contributing to this.

Mealtime answered 13/11, 2018 at 2:4 Comment(3)
Try removing the paragraph tags <p>Matney
@Matney I'll try that, and considered it earlier, but notice that there are no <p> tags in the first instance, the <h2>Student did poorly</h2> one. It exhibits the same behavior as the one and I have to handle that kind of html.Mealtime
@Matney Removing the <p> tags definitely allowed the label to shrink as needed. Thanks. Any ideas on why the <h2> tags are causing line breaks after the end of the text, and what to do about it? One thing that's unclear is why that one attributed string has two sections rather than one (two fonts rather than one).Mealtime
J
9

I faced the same problem where needed to delete the unwanted newlines added by my <p> tags and was not able to change things on server side. So I came first with this solution:

Swift

while !attributedString.string.isEmpty //Validates if the attributed string has at least one character
    && CharacterSet.newlines.contains(attributedString.string.unicodeScalars.last!) //Compares if the last unicode character of the attributed string is inside the `CharacterSet` of newlines
    {
    attributedString.deleteCharacters(in: NSRange(location: attributedString.length - 1, length: 1)) //Deletes the last character of the attributed string
}

Obj-C (not tested)

while ([attributedString.string length] > 0
    && [[attributedString.string substringFromIndex:[attributedString.string length] - 1] rangeOfCharacterFromSet:NSCharacterSet.newlineCharacterSet].location != NSNotFound)
    {
    [attributedString deleteCharactersInRange:NSMakeRange([attributedString length] - 1, 1)];
}

(I execute this code right after converting the html code into an attributed string.)


But I can see by the answers that also the <h#> tags adds newlines. So for a extended solution could be something like make an attributed string for every pair of tags; apply them the filter above; and after that, join them in one attributed string or display them in different labels.


Edit

I use an extension of String to convert the HTML code to an NSMutableAttributedString:

extension String {

    /// Returns a new attributed string loading any HTML tags that the receiver contains.
    ///
    /// If the HTML code is malformed or can not format it, this method returns the receiver but as a `NSMutableAttributedString`. If the formated resulting string contains one or more newlines at the end, they are removed.
    func htmlFormat() -> NSMutableAttributedString {
        var attributedString = NSMutableAttributedString(string: self, attributes: [.font : UIFont.preferredFont(forTextStyle: .body)])

        if let data = data(using: .utf8),
            let formatedString = try? NSMutableAttributedString(data: data,
                                                                options: [.documentType: NSAttributedString.DocumentType.html,
                                                                          .characterEncoding: String.Encoding.utf8.rawValue],
                                                                documentAttributes: nil) {
            attributedString = formatedString
        }

        while !attributedString.string.isEmpty
        && CharacterSet.newlines.contains(attributedString.string.unicodeScalars.last!) {
            attributedString.deleteCharacters(in: NSRange(location: attributedString.length - 1, length: 1))
        }

        return attributedString
    }

}
Jailbird answered 26/11, 2018 at 19:13 Comment(8)
There are no extra newline characters at the end of the attributed string, so the deleteCharacters call you suggest will only delete a character that needs to remain. The extra space must be resulting somehow from the actual attributed string formatting itself.Mealtime
When XCode prints the description of an attributed string with no newlines, then the { brace is printed in the same line, but if it has one then the brace is printed in the next line. I mention this to visually check if it has a newline even if it is not marked as "\n". And maybe you are correct about that the problem is the attributed string format itself, but I can not figure out where exactly, so at least this solution works fine for me. Maybe you can try it with an if instead of a while in order to only delete one newline.Leyes
Are you sure this worked for you? deleteCharactersInRange only compiles successfully for an NSMutableString (not an NSAttributedString or an NSString). I can't get your code to run so far.Mealtime
Yes, but as you mentioned (and I forgot to clarify), the attributed string has to be an NSMutableAttributedString in order to make changes to it.Leyes
The newlines you are talking about are added by Xcode's console when it prints out the NSAttributed string (or a NSDictionary, or other object) in order to format the text so the console text will be easily readable. But I am trying to get the UILabel to render the attributed string without drawing extra space. So you are able to convert <h2>some-text</h2> to an attributed string and reproduce this problem? And then are able to modify an NSMutableAttributedString in a way that fixes it? By all means post all your code, code that converts your html to an attributed string and fixes it.Mealtime
I have confirmed this works as intended. To see the newline more clearly, do print(label.text) after setting the attributed string to the label.Petr
I can confim that the <h2> adds a little padding above itself, making it looking not centered. I do not know if this is intended or a little bug from Apple(?). But at least, I am able to erase the newlines at the end, too. I update my answer, including my convertion from the html to an attributed string. And if I can solve the problem of the space above the tag, I will also upload it.Leyes
You are right, @ÁngelTéllez. There was a newline (int value 10) character at end of the string and removing it did just what you said it would, and what I needed. Muchas gracias.Mealtime

© 2022 - 2024 — McMap. All rights reserved.