Skip to content

Instantly share code, notes, and snippets.

@algal
Last active September 21, 2015 19:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save algal/17c73d28e562ccc07372 to your computer and use it in GitHub Desktop.
Save algal/17c73d28e562ccc07372 to your computer and use it in GitHub Desktop.
Playground gist illustrating relation between line and paragraph breaks, and between line spacing and paragraph spacing
import UIKit
//: # Paragraphs, lines, breaks, and spacing in attributed strings
/*:
The cocoa text system lets you separately adjust line spacing and paragraph spacing.
This playground investigates a few questions concerning these spacing configurations and text layout:
1. Do empty UILabels collapse to zero height?
2. What defines the difference between a line break and a paragraph break, for the purpose of whether line spacing or paragraph spacing is used?
3. What kind of "spacing collapse" behavior applies?
What motivates question 3 in particular is the alternative example of the HTML5 box model. In this model, the `<p>` paragraph elements define boxes with vertical margin distances. There is a relatively complicated rule which requires when margins "collapse" into each other and overlap to define a shared margin. A paragraph's margins can collapse with its sibling paragraph, with its parent block, etc..
Answers:
1. Yes, empty UILabels collapse to zero height.
2. It seems paragraph breaks are defined by _either_ the LINE FEED or PARAGRAPH SEPARATOR characters, and line breaks are defined by word wrapping. (Maybe there is also Unicode character which expicitly defines a soft line break?)
3. There is no spacing collapse behavior. Paragraph spacing is just added to line spacing.
*/
//: ## Question 1: Do empty labels collapse?
let oneLineLabel = UILabel()
oneLineLabel.text = "Hello World"
let ics = oneLineLabel.intrinsicContentSize()
let stf = oneLineLabel.sizeThatFits(CGSize.zero)
let emptyLabel = UILabel()
emptyLabel.text = ""
let emptyICS = emptyLabel.intrinsicContentSize()
//: Answer: yes, empty labels collapse to zero height.
//: ## Question 2: Does the PARAGRAPH SEPARATOR characater break paragraphs and is paragraph spacing different from line spacing by default?
let twoLineLabel = UILabel()
twoLineLabel.numberOfLines = 0
twoLineLabel.text = "Hello\nWorld"
let twolineICS = twoLineLabel.intrinsicContentSize()
let twoParaLabel = UILabel()
twoParaLabel.numberOfLines = 0
twoParaLabel.text = "Hello\u{2029}World"
let twoParaICS = twoParaLabel.intrinsicContentSize()
//: Answer: uncertain. The heights of these two labels are identical, despite that one uses LINE FEED and the other uses PARAGRAPH SEPARATOR. So either PARAGRAPH SEPARATOR does not break paragraphs, or `lineSpacing` and `paragraphSpacing` are indistiguishable.
//: ## Narrowed Question 3: What defines paragraph breaks, for the purpose of distinguishing `lineSpacing` and `paragraphSpacing`?
//: Let us set explicit distinct values for line spacing and paragraph spacing
let p = NSMutableParagraphStyle()
let ps = p.paragraphSpacing
let psb = p.paragraphSpacingBefore
p.lineSpacing = 5
p.paragraphSpacing = 10
twoLineLabel.attributedText = NSAttributedString(string: twoLineLabel.text!, attributes: [NSParagraphStyleAttributeName:p])
twoParaLabel.attributedText = NSAttributedString(string: twoParaLabel.text!, attributes: [NSParagraphStyleAttributeName:p])
let twolineICSP = twoLineLabel.intrinsicContentSize()
let twoParaICSP = twoParaLabel.intrinsicContentSize()
//: Answer: Both have the same hieght. So the paragraph separator does not cause `paragraphSpacing` to be used in one label but not the other. In fact both are 15 taller than the default case without custom spacing, which is the sum of the `lineSpacing` and `paragraphSpacing`. So it seems both values are being added, without any collapsing of vertical space (unlike the HTML5 box model). So it seems that: _both LINE FEED and PARAGRAPH SEPARATOR are being treated as defining a new paragraph_.
//: ## Question 4: So what defines a line break, not a paragraph break?
//: Let us check to see if the difference between a line break and a paragraph break is just that a line break is produced by word wrapping, and a paragraph break is forced by the LINE FEED character. We will setup one label with a space and with a LINE FEED. We will request the labels' sizes, forced to a width which requires a word wrapping at the space. Then we will observe the resulting heights, to see if the break produced by word wrapping has a different height from the break produced by the LINE FEED.
twoLineLabel.attributedText = NSAttributedString(string: "Hello World", attributes: [NSParagraphStyleAttributeName:p])
twoParaLabel.attributedText = NSAttributedString(string: "Hello\nWorld", attributes: [NSParagraphStyleAttributeName:p])
let wrappedTwoLineFittingSize = twoLineLabel.systemLayoutSizeFittingSize(CGSize(width: 50, height: 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel)
let wrappedTwoParaFittingSize = twoParaLabel.systemLayoutSizeFittingSize(CGSize(width: 50, height: 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel)
//: Answer: Yes, the word-wrapped break uses the `lineSpacing` height, and the LINE FEED-forced break uses the `lineSpacing`+`paragraphSpacing` height. So _word wrapping defines line breaks, LINE FEEDs defines paragraph breaks, and the vertical space between paragraphs is the sum of the `lineSpacing` and `paragraphSpacing`_.
//: ## Question: does LINE SEPERATOR cause a line not parapgraph break?
twoLineLabel.attributedText = NSAttributedString(string: "Hello\u{2029}World", attributes: [NSParagraphStyleAttributeName:p])
twoParaLabel.attributedText = NSAttributedString(string: "Hello\u{2028}World", attributes: [NSParagraphStyleAttributeName:p])
let twolineICSP2 = twoLineLabel.intrinsicContentSize()
let twoParaICSP2 = twoParaLabel.intrinsicContentSize()
//: Answer: Nope, nope, nope. Despite the recommendation in the gloriously nerdy section 5.8 of the Unicode standard, which is that LINE SEPARATOR and PARAGRAPH SEPARATOR should be treated differently, where one marks intra-paragraph line breaks, and othe other marks paragraph breaks, the Cocoa text system treats them both as paragraph separators, as it applies the paragraph spacing to both.
//: Question: is the default lineSpacing in a multiline label, the same as the result of directly stacking two labels?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment