NSAttributedString和HTML样式(项目符号对齐)

14
在我的iOS应用程序中,我使用NSAttributedString来生成一个项目符号列表。不幸的是,我在使这些项目符号看起来好看方面遇到了困难。我的第一次尝试是使用常规文本和Unicode字符来表示项目符号,基本上使用像这样的字符串:

在我的iOS应用程序中,我使用NSAttributedString来生成一个项目符号列表。不幸的是,我在使这些项目符号看起来好看方面遇到了困难。我的第一次尝试是使用常规文本和Unicode字符来表示项目符号,基本上使用像这样的字符串:

var attributedString = NSMutableAttributedString(
    string: "Here is a list of bullets and a paragraph introducing them, note that this paragraph spans multiple lines\n" +
    "• This is the first bullet\n" +
    "• Here is a second bullet\n" +
    "• And here is a third bullet with a lot of text such that it overflows to the next line"
)

结果如下:

enter image description here

我喜欢这些项目符号的外观,但是最后一个项目符号中溢出的文本应该与前一行对齐,我无法通过纯文本实现这一点(而不将相同的对齐方式应用于上面的段落)。

我的第二次尝试是使用NSAttributedString中的NSHTMLTextDocumentType进行HTML处理,并使用<ul><li>元素生成项目符号。

let content = "Here is a list of bullets and a paragraph introducing them, note that this paragraph spans multiple lines" +
    "<ul>" +
         "<li>This is the first bullet</li>" +
         "<li>Here is a second bullet</li>" +
         "<li>And here is a third bullet with a lot of text such that it overflows to the next line</li>" +
    "</ul>"
var attributedString = try! NSMutableAttributedString(
    data: content,
    options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
    documentAttributes: nil
)

问题已经解决了,但引入了一个新的问题:

输入图像描述

现在项目符号之间的距离太远了(从左边缘和右边的文本都太远)。我尝试使用典型的HTML/CSS技巧来修复对齐问题(<li style="text-indent: -10px;">),但这些样式似乎被NSAttributedString忽略了。

我尝试使用另外一个NSMutableParagraphStyle来解决这个问题,但它似乎带来了更多的问题。下面是我尝试过的内容:

var paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.firstLineHeadIndent = 0
paragraphStyle.headIndent = 20
attributedString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: attributedStringRange)

这是我得到的:

输入图像描述

正如您所看到的,它只会使事情变得更糟,以下是我的问题:

  • 虽然它确实偏移了第二行,但我只希望该效果适用于项目符号,而不是段落前面(我想我可以通过减少应用效果的范围来弥补这一点)
  • 我必须猜测/硬编码偏移量,在我的示例中,我选择了20,但对于给定的当前字体设置,这对于项目符号来说还不够,而这些设置可能会发生变化
  • 由于某种原因,无缘无故地拉伸了项目符号间距,似乎只需应用普通的NSParagraphStyle就会出现这种情况,我看不到任何修复此问题的选项。

我真正想要的是,我的项目符号间距看起来类似于第一个屏幕截图,同时第二行的溢出缩进看起来像第二个屏幕截图,而无需硬编码精确的像素位置。你们能帮帮我吗?

谢谢


你能找到一个好的解决方案吗? - flohei
很遗憾,我不再记得解决方案是什么了,我认为它是发布的答案的某种变体。我想最终我使用了独立元素来处理顶部段落和项目列表,这感觉像是一种妥协,但不值得再花更多时间去解决。 - Alexander Tsepkov
我明白了。这正是我试图避免的,因为我从我们的API获取整个文本,我不想对结果进行干扰,因为它们可能会有很大的变化。所以我可能会遇到一个接一个的异常。无论如何,感谢您的回答! - flohei
那你做了什么?我有完全相同的问题。 - Sonic Master
3个回答

8

我需要给列表添加自定义样式,以下是我为NSAttributedString中每个项目符号使用的段落样式

headIndentfirstLineHeadIndent可以更改,但NSTextTab位置应与headIndent相同

NSMutableParagraphStyle *const bulletParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
bulletParagraphStyle.headIndent = 60;
bulletParagraphStyle.firstLineHeadIndent = 30;
NSTextTab *listTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentNatural location:60 options:@{}];
bulletParagraphStyle.tabStops = @[listTab];

这个方法假设你的项目符号后面有一个 \t (然后是文本)。

1
谢谢,这几乎可以了,现在我只是试图将顶部段落的缩进与项目符号分开(现在正在尝试使用NSRange,但它并不合作,所以一旦我弄清楚了,我会回来解决这个问题)。 - Alexander Tsepkov
1
有同样的问题,可以使用HTML <table>布局轻松解决,但不太喜欢使用表格。 - Injectios

5

使用<table>代替<ul><li>以处理项目符号的对齐问题。类似于ul{margin:0;padding:0}li{margin-left: 0;}之类的样式将被忽略。

示例:

let html = "<table>" +
            "<tr><td valign=\"top\" style=\"padding-right:16px\">•</td><td valign=\"top\">text 1</td></tr>" +
            "<tr><td valign=\"top\" style=\"padding-right:16px\">•</td><td valign=\"top\">text 2</td></tr>" +
            "</table>"

let attributedString = try! NSAttributedString(data: html.data(using: String.Encoding.utf8)!,
                               options: [.documentType: NSAttributedString.DocumentType.html,
                                         .characterEncoding: String.Encoding.utf8.rawValue,],
                               documentAttributes: nil)

1
真聪明的方式! - Okhan Okbay

0

这是我另一种实现方式:

    attributedString.enumerateAttribute(.paragraphStyle,
                                        in: NSRange(location: 0, length: attributedString.length),
                                        options: [])
    { (paragraphStyle, range, _) in
        guard let paragraphStyle = paragraphStyle as? NSParagraphStyle else { return }
        let updatedStyle = NSMutableParagraphStyle()
        updatedStyle.setParagraphStyle(paragraphStyle)
        
        // Here we can modify paragraphs. Also could add condition to update only paragraphs with lists.
        updatedStyle.firstLineHeadIndent = 0
        updatedStyle.headIndent = 20
        
        attributedString.addAttribute(.paragraphStyle, value: updatedStyle, range: range)
    }

主要思想是,不是创建一个段落(据我所知会破坏事物),而是修改现有的段落。为了实现这一点,我使用enumerateAttribute(...),它可以给我所有现有段落及其范围。这个问题引导我想到了这个思路:Get the range of a paragraph NSAttributedString

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接