13

How to remove extra padding when converting HTML to NSAttributedString

 2 years ago
source link: https://sarunw.com/posts/how-to-remove-extra-padding-when-converting-html-to-nsattributedstring/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How to remove extra padding when converting HTML to NSAttributedString

21 Oct 2021 ⋅ 6 min read ⋅ UIKit HTML

Table of Contents

We learned from How to display HTML in UILabel and UITextView a way to convert HTML string to an NSAttributedString for using in UILabel or UITextView.

You might not be aware that sometimes, you will get a bottom padding at the end of your result NSAttributedString. Learn what is causing this and how to mitigate the problem.

You can easily support sarunw.com by checking out this sponsor.

codeshot.png Sponsor sarunw.com and reach thousands of iOS developers.

Problem when converting HTML string to NSAttributedString

When you convert HTML string to NSAttributedString, sometimes, you get an extra unwanted bottom padding. Here is how the problem looks like.

I paint UILabel with a pink background to show its bounds.

Different NSAttributedString for different HTML strings.Different NSAttributedString for different HTML strings.

And here is the code that produce above result.

let label1 = UILabel()
label1.backgroundColor = .systemPink
label1.numberOfLines = 0

let label2 = UILabel()
label2.backgroundColor = .systemPink
label2.numberOfLines = 0

let label3 = UILabel()
label3.backgroundColor = .systemPink
label3.numberOfLines = 0

// 1
let text1 = "This is a <b>bold</b> text."
let text2 = "<p>This is a <b>bold</b> text.</p>"
let text3 = "<div>This is a <b>bold</b> text.</div>"

// 2
label1.attributedText = text1.htmlAttributedString()
label2.attributedText = text2.htmlAttributedString()
label3.attributedText = text3.htmlAttributedString()

// 3
extension String {
func htmlAttributedString() -> NSAttributedString? {
let htmlTemplate = """
<!doctype html>
<html>
<head>
<style>
body {
font-family: -apple-system;
font-size: 24px;
}
</style>
</head>
<body>
\(self)
</body>
</html>
"""

guard let data = htmlTemplate.data(using: .utf8) else {
return nil
}

guard let attributedString = try? NSAttributedString(
data: data,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil
) else {
return nil
}

return attributedString
}
}

1 Our example HTML strings.
2 We convert HTML string to NSAttributedString and assign them to each label. I leave out layout/constraint code for brevity.
3.htmlAttributedString() is a helper method that convert HTML string to NSAttributedString. You can read more about it here.

Can you guess the cause of the extra padding?

Cause of the problem

This problem is caused when two conditions are met.

  1. Your HTML string contains an element with a block display behavior, such as div and p.
  2. You have multiple lines UILabel (numberOfLines = 0).

Your HTML string contains an element with a block display behavior

Block is a display behavior in HTML which displays an element that starts on a new line and would take up the whole width.

From mozilla.org

block
The element generates a block element box, generating line breaks both before and after the element when in the normal flow.

UIKit does a good job preserving this semantic and adding a new line (\n) and space for each display block element.

This example has multiple paragraphs (<p>), and having a new line after each paragraph makes our text more readable.

let loremString = """
<p>Lorem ipsum dolor sit amet, qui eu scripta liberavisse. Ei est case fastidii apeirian, an possim regione expetenda sed.</p>
<p>Est at alii solum. Per unum elit ad. At mel everti habemus. Munere philosophia id mea, omnes postea reprimique ne eum.</p>
<p>Euismod inimicus sea ne, pri tota senserit ut.</p>
"""

label.attributedText = loremString.htmlAttributedString()

It is good to know this is expected behavior, but you might still want to remove the last paragraph's padding anyway.

UIKit add a new line character for display block.UIKit add a new line character for display block.

Space might vary based on an HTML element as you can see in the first section where div got a smaller padding then p.

You have multiple lines UILabel

Since the problem is about a new line character, this problem only shows up when you have UILabel that supports multiple lines (numberOfLines = 0).

How to remove unwanted bottom padding

There are many ways to tackle this problem, but I will tell you two solutions that I know so far.

  1. Remove a new line from NSAttributedString.
  2. Solve it with CSS.

Remove new line from NSAttributedString

The first solution is straightforward. Since we know that the extra bottom padding at the end is a new line character, we can write a code to remove it from our NSAttributedString.

Here is one way of doing it.

extension NSAttributedString {
func trimmedAttributedString() -> NSAttributedString {
let nonNewlines = CharacterSet.whitespacesAndNewlines.inverted
// 1
let startRange = string.rangeOfCharacter(from: nonNewlines)
// 2
let endRange = string.rangeOfCharacter(from: nonNewlines, options: .backwards)
guard let startLocation = startRange?.lowerBound, let endLocation = endRange?.lowerBound else {
return self
}
// 3
let range = NSRange(startLocation...endLocation, in: string)
return attributedSubstring(from: range)
}
}

1 Find first non-whitespace character and new line character.
2 Find last non-whitespace character and new line character.
3 Getting range out of locations. This trim out leading and trailing whitespaces and new line characters.

Then use it like this.

label.attributedText = loremString.htmlAttributedString()?.trimmedAttributedString()
Trim leading and trailing whitespaces and new line characters from NSAttributedString.Trim leading and trailing whitespaces and new line characters from NSAttributedString.

As you can see in our helper method, we can apply CSS style by modifying <style> in the htmlTemplate.

extension String {
func htmlAttributedString() -> NSAttributedString? {
let htmlTemplate = """
<!doctype html>
<html>
<head>
<style>
body {
font-family: -apple-system;
font-size: 24px;
}
</style>
</head>
<body>
\(self)
</body>
</html>
"""

...
}
}

An extra newline character (\n) is added only for a block display element. To fix this with CSS, we have to change the display property of the last p tag to a non-block display type.

extension String {
func htmlAttributedString() -> NSAttributedString? {
// 1
let htmlTemplate = """
<!doctype html>
<html>
<head>
<style>
body {
font-family: -apple-system;
font-size: 24px;
}
p:last-child {
display: inline;
}
</style>
</head>
<body>
\(self)
</body>
</html>
"""

guard let data = htmlTemplate.data(using: .utf8) else {
return nil
}

guard let attributedString = try? NSAttributedString(
data: data,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil
) else {
return nil
}

return attributedString
}
}

1 We add a new CSS p:last-child which will matches last p tag and set display to inline.

Use CSS to change display type of the last p tag.Use CSS to change display type of the last p tag.

The CSS selector I use here only removes the new line for the last p tag. If you have a different HTML, you might need to modify it to suit your need.

p:last-child {
display: inline;
}

You can easily support sarunw.com by checking out this sponsor.

codeshot.png Sponsor sarunw.com and reach thousands of iOS developers.

Conclusion

When converting HTML string to NSAttributedString, ** all display block elements such as div and p will produce an extra newline at the end**. I have shown you two ways to mitigate this problem, CSS and trimming method.

Using CSS requires less code but might require some CSS knowledge to create the right selector for your content. On the other hand, trimming method requires more code, but you don't have to know about CSS or the content of your HTML string.


You may also like

How to display HTML in UILabel and UITextView

When you work with an API, there would be a time when your backend wants to control a text style, and HTML is the most common format for the job. Do you know that WKWebView is not the only way to present HTML string? Learn how to render it in UILabel and UITextView.

UIKit HTML
How to change a back button image

Learn how to change a UINavigationBar back button indicator.

UIKit
Scaling custom fonts automatically with Dynamic Type

Font is an essential part of an app. A good selection of font would make your app stand out from the crowd. But whatever fonts you choose, you have to make sure it doesn't lose its core function, readability. You might feel reluctant to use a custom font in the past because you might lose the benefit of dynamic type goodness that Apple provides with their system font. Since iOS 11, this is no longer the case. You can easily use your custom font dynamic type.

UIKit

Read more article about UIKit, HTML,

or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron

Buy me a coffee

Tweet

Share

Previous
How to make a SwiftUI view to fill its container width and height

We can use a frame modifier to make a view appear to be full width and height, but the result might not be what you expected.

Next
How to add custom fonts to iOS app

In iOS, you can add and use a custom font to your app and use it like other assets. The process is not hard, but it might not be straightforward as adding an image. Let's learn how to do it.

← Home


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK