Внешний вид противоречит коду, в Simple AttributedString NSParagraphStyle отображается в UITextView
Я пытаюсь вручную проанализировать строку уценки и создать атрибутную строку для UITextView.
Когда я пытаюсь форматировать блоки кода, я обнаружил странную ошибку, которую не могу решить. Я сократил проблему до этого минимального количества демонстрационного кода. Я надеюсь, что кто-то может мне помочь.
Проблема:
Все блоки имеют одинаковый стиль. Но одни и те же атрибуты приводят к разному внешнему виду, что видно после первых трех обратных кавычек в каждом блоке кода.
Первый блок работает нормально. Но в приведенных ниже есть этот странный дополнительный пробел между первой строкой и второй строкой.
3 блока стилизованы с использованием цикла for с идентичным кодом.
Код
Я сократил код и поместил его в один файл быстрой игровой площадки. И я очень стараюсь сделать пост легким для чтения и понимания, чтобы мне могли помочь.
Код прямого стиля занимает менее 10 строк, может быть, 7 строк.
Я разделил код на 3 части в своем посте для удобства чтения. Но их можно собрать напрямую и запустить на быстрой игровой площадке.
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let str = """
`` `
some
`` `
`` `
some
`` `
`` `
some
`` ` <- (conflicting with stackoverflow markdown editor, so i added extra space)
"""
var at = AttributedString(stringLiteral: str)
/* === ============= === */
/* === styling start === */
/* === ↓ ↓ ↓ ↓ ↓ ↓ ↓ === */
// manually counted range and indexes for 3 code blocks
for range in [at.intToRange(start: 0, end: 12),
at.intToRange(start: 14, end: 26),
at.intToRange(start: 28, end: 40)] {
/* apply code block attributes style */
at[range].mergeAttributes(.codeBlock)
// "lower" is the range of head 3 `
let lower = range.lowerBound..<at.index(range.lowerBound, offsetByCharacters: 3)
// "upper" is the range of tail 3 `
let upper = at.index(range.upperBound, offsetByCharacters: -3)..<range.upperBound
/* apply code block prefix ` and suffix ` attributes */
at[lower].mergeAttributes(.codeBlockPrefixBacktick)
at[upper].mergeAttributes(.codeBlockSuffixBacktick)
// still in loop
/* === ↑ ↑ ↑ ↑ ↑ ↑ === */
/* === styling end === */
/* === =========== === */
/* print out prefix and suffix paragraph attributes */
/* ↓ this part has nothing to do with styling ↓ */
[lower, upper].forEach {
for i in 0..<3 {
let a = at.index($0.lowerBound, offsetByCharacters: i)
let b = at.index($0.lowerBound, offsetByCharacters: i+1)
let c = at[a..<b]
print(c.characters[a])
print(c[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute])
}
}
}
/* Give the AttributedString To UITextView and display */
let tv2 = UITextView()
tv2.isScrollEnabled = true
tv2.frame = CGRectMake(0, 0, 600, 800)
tv2.attributedText = NSAttributedString(at)
view.addSubview(tv2)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Предустановки стиля : 3 основных компонента: шрифты, отступы и интервалы.
extension AttributeContainer {
// code block main body style
static let codeBlock = AttributeContainer
.font(UIFont(name: "Menlo", size: 15)!)
.paragraphStyle(AttributeContainer.codeBlockStyle)
.backgroundColor(UIColor.lightGray)
private static let codeBlockStyle:NSParagraphStyle = {
let style = NSMutableParagraphStyle()
style.headIndent = 12
style.firstLineHeadIndent = 12
style.tailIndent = 12
return style
}()
// code block prefix style
static let codeBlockPrefixBacktick = AttributeContainer
.font(UIFont(name: "Menlo", size: 17)!)
.paragraphStyle(AttributeContainer.codeBlockPrefixStyle)
.backgroundColor(UIColor.lightGray)
private static let codeBlockPrefixStyle:NSParagraphStyle = {
let style = NSMutableParagraphStyle()
style.headIndent = 12
style.firstLineHeadIndent = 12
style.tailIndent = 12
style.paragraphSpacingBefore = 24
style.paragraphSpacing = 0
return style
}()
// code block suffix style
static let codeBlockSuffixBacktick = AttributeContainer
.font(UIFont(name: "Menlo", size: 17)!)
.paragraphStyle(AttributeContainer.codeBlockSuffixStyle)
.backgroundColor(UIColor.lightGray)
static let codeBlockSuffixStyle:NSParagraphStyle = {
let style = NSMutableParagraphStyle()
style.headIndent = 12
style.firstLineHeadIndent = 12
style.tailIndent = 12
style.paragraphSpacingBefore = 0
style.paragraphSpacing = 24
return style
}()
}
Некоторые удобные функции
extension AttributedString {
/// convert ints to range
func intToRange(start:Int, end:Int) -> Range<Index> {
let lower = convertIntToIndex(int: start)
let upper = convertIntToIndex(int: end)
return lower..<upper
}
/// convert Int to String.Index
func convertIntToIndex(int:Int) -> Index {
characters.index(startIndex, offsetBy: int)
}
/// get character at index i
/// string[i]
subscript(i:Index) -> Character {
return characters[i]
}
}
Я считаю, что стили этих трех блоков идентичны, потому что я использовал только цикл for с идентичным кодом.
Когда я попытался распечатать их стиль, проблем тоже не возникло. Похоже, внешний вид противоречит коду.
Вот напечатанный результат каждой отдельной обратной кавычки (`), всего 18, посмотрите на
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 24, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '(null)')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
`
Optional(Alignment 4, LineSpacing 0, ParagraphSpacing 24, ParagraphSpacingBefore 0, HeadIndent 12, TailIndent 12, FirstLineHeadIndent 12, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
28L,
56L,
84L,
112L,
140L,
168L,
196L,
224L,
252L,
280L,
308L,
336L
), DefaultTabInterval 0, Blocks (
), Lists (
), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (
) ListIntentOrdinal 0 CodeBlockIntentLanguageHint '')
Заранее спасибо, если кто поможет.