Swift中NSAttributedString无序列表字体异常及替代方案咨询
我最近在把HTML字符串转换成NSAttributedString时遇到了个麻烦:给body设置样式后,普通文本的字体能正常改成我指定的-apple-system、Avenir-Medium这些,但只要HTML里包含<ul>和<li>的无序列表,列表项的字体就完全不受样式影响,哪怕我单独给ul、li加样式也没用。
我成功修改普通文本的代码是这样的:
let myOriginalString: String = "<!DOCTYPE html> <head> <style type=\"text/css\"> body{font-family: '-apple-system','Avenir-Medium','Avenir-Black'; font-size:14;} </style> <body> My HTML content </body>" let data: Data = myOriginalString.data(using: .utf8)! let myConvertedString: NSAttributedString = try! NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
但换成带列表的HTML就不行了:
let myOriginalString: String = "<!DOCTYPE html> <head> <style type=\"text/css\"> body{font-family: '-apple-system','Avenir-Medium','Avenir-Black'; font-size:14;} </style> <body> My HTML content <ul> <li>Item 1</li> <li>Item 2</li> </ul> </body>" let data: Data = myOriginalString.data(using: .utf8)! let myConvertedString: NSAttributedString = try! NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
想请教下:为什么只有无序列表的字体没法正常渲染?有没有不依赖HTML来创建带无序列表的NSAttributedString的更好方法?
为什么列表项字体不生效?
这其实是iOS的NSAttributedString解析HTML时的一个小坑——当解析<ul>和<li>标签时,系统会给列表项默认应用系统自带的列表字体样式,这个样式的优先级会比你在head里设置的body样式更高。哪怕你直接给li加font-family,也会被系统的默认样式覆盖。
本质上是因为NSAttributedString解析HTML依赖的WebKit引擎,对列表元素有内置的样式规则,这些规则会优先于用户自定义的body级样式。
不依赖HTML创建无序列表NSAttributedString的更优方式
完全可以直接通过NSMutableAttributedString来构建带无序列表的富文本,不需要绕HTML的弯路,灵活性也更高。这里给你两种常用的方法:
方法1:手动添加列表符号+缩进
这种方法适合简单的列表,直接给每个列表项添加项目符号(比如●),再设置段落缩进:
func createBulletedList(items: [String], font: UIFont) -> NSAttributedString { let mutableAttrString = NSMutableAttributedString() let paragraphStyle = NSMutableParagraphStyle() // 设置缩进:左边留足够空间放符号,首行缩进抵消符号的宽度 paragraphStyle.headIndent = 20 paragraphStyle.firstLineHeadIndent = -20 let attributes: [NSAttributedString.Key: Any] = [ .font: font, .paragraphStyle: paragraphStyle ] for item in items { // 先加符号,再加列表文本 let bulletPoint = "● " let bulletAttrString = NSAttributedString(string: bulletPoint, attributes: attributes) let itemAttrString = NSAttributedString(string: "\(item)\n", attributes: attributes) mutableAttrString.append(bulletAttrString) mutableAttrString.append(itemAttrString) } // 移除最后多余的换行 if mutableAttrString.length > 0 { mutableAttrString.deleteCharacters(in: NSRange(location: mutableAttrString.length - 1, length: 1)) } return mutableAttrString } // 使用示例 let listFont = UIFont(name: "Avenir-Medium", size: 14)! let listAttrString = createBulletedList(items: ["Item 1", "Item 2"], font: listFont)
方法2:使用NSParagraphStyle的listStyle(iOS 11+)
iOS 11及以后,系统提供了更原生的列表样式支持,直接通过NSParagraphStyle的listStyle属性来设置,不需要手动加符号:
func createNativeBulletedList(items: [String], font: UIFont) -> NSAttributedString { let mutableAttrString = NSMutableAttributedString() let paragraphStyle = NSMutableParagraphStyle() // 设置为无序列表样式 paragraphStyle.listStyle = .disc // 可调整列表缩进距离 paragraphStyle.listIndent = 16 let attributes: [NSAttributedString.Key: Any] = [ .font: font, .paragraphStyle: paragraphStyle ] for (index, item) in items.enumerated() { let itemString = index == items.count - 1 ? item : "\(item)\n" let itemAttrString = NSAttributedString(string: itemString, attributes: attributes) mutableAttrString.append(itemAttrString) } return mutableAttrString } // 使用示例 let listFont = UIFont(name: "Avenir-Medium", size: 14)! let nativeListAttrString = createNativeBulletedList(items: ["Item 1", "Item 2"], font: listFont)
这种方法的好处是系统会自动处理列表符号的样式、对齐和缩进,比手动添加符号更规范,也更适配不同系统版本的样式。
内容的提问来源于stack exchange,提问作者user2876886

