You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS中使用enumerateAttributes调整TabStops,解决UITextView嵌套列表边距问题

Fixing Extra Padding & Alignment Issues for Nested HTML Lists in UITextView (Swift)

I’ve run into this exact issue before—UIKit’s default HTML parser tends to stack up tab intervals for nested <ul> elements, leading to that wonky over-indented look you’re seeing, and CSS padding/margin fixes often don’t play nice with UITextView’s attributed string rendering. Your current code adjusts tab stops once, but it doesn’t account for how nested levels multiply the indentation. Here’s how to fix it:

Step 1: Adjust HTML Conversion with Base CSS (Optional but Helpful)

First, tweak your HTML before converting it to an attributed string to reset default list margins/padding. This cuts down on UIKit’s built-in spacing that’s hard to override later:

func convertHtmlToAttributedString(_ html: String) -> NSAttributedString? {
    // Inject CSS to reset list styles at the source
    let styledHtml = html.replacingOccurrences(
        of: "<ul>", 
        with: "<ul style=\"margin: 0; padding-left: 20px;\">"
    )
    
    guard let data = styledHtml.data(using: .utf8) else { return nil }
    
    let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
        .documentType: NSAttributedString.DocumentType.html,
        .characterEncoding: String.Encoding.utf8.rawValue,
        .defaultAttributes: [
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14, weight: .regular)
        ]
    ]
    
    do {
        return try NSAttributedString(data: data, options: options, documentAttributes: nil)
    } catch {
        print("HTML conversion error: \(error.localizedDescription)")
        return nil
    }
}

Step 2: Fix Tab Stops & Indentation via Swift Code

The core fix is calculating the nesting level of each list item and setting consistent indentation per level, instead of just subtracting a single tab interval. Replace your existing attributed string processing code with this:

guard let attributedQuestionMessage = convertHtmlToAttributedString(yourHtmlString) else { return }

let mutableAttributedString = NSMutableAttributedString(attributedString: attributedQuestionMessage)
let indentPerLevel: CGFloat = 20.0 // Adjust this to control how much each nested level indents

mutableAttributedString.enumerateAttributes(
    in: NSRange(location: 0, length: mutableAttributedString.length), 
    options: []
) { (attributes, range, _) in
    guard let originalStyle = attributes[.paragraphStyle] as? NSParagraphStyle else { return }
    
    let mutableStyle = originalStyle.mutableCopy() as! NSMutableParagraphStyle
    
    // Calculate nesting level: UIKit adds one tab stop per nested list level
    let nestingLevel = mutableStyle.tabStops.count
    
    // Reset tab stops to match our consistent per-level indent
    var adjustedTabs: [NSTextTab] = []
    for level in 0..<nestingLevel {
        let tabLocation = indentPerLevel * CGFloat(level + 1)
        adjustedTabs.append(NSTextTab(textAlignment: .left, location: tabLocation, options: [:]))
    }
    
    // Set indents to align list content properly
    mutableStyle.firstLineHeadIndent = indentPerLevel * CGFloat(nestingLevel)
    mutableStyle.headIndent = indentPerLevel * CGFloat(nestingLevel)
    mutableStyle.tabStops = adjustedTabs
    mutableStyle.defaultTabInterval = indentPerLevel
    
    // Update the attributed string with our fixed style
    mutableAttributedString.removeAttribute(.paragraphStyle, range: range)
    mutableAttributedString.addAttribute(.paragraphStyle, value: mutableStyle, range: range)
}

textView.attributedText = mutableAttributedString

How This Works:

  • Nesting Level Detection: UIKit automatically adds a tab stop for each nested <ul> layer, so we use the count of tab stops to determine how deep a list item is.
  • Consistent Indentation: Instead of letting UIKit stack default tab intervals, we explicitly set each tab stop to indentPerLevel * (level + 1), ensuring each nested level indents by the same amount.
  • Indent Alignment: Setting firstLineHeadIndent and headIndent ensures the list content lines up correctly with the bullet points, no matter how deep the nesting goes.

This should eliminate those extra padding gaps and fix the misalignment in your nested lists.

内容的提问来源于stack exchange,提问作者Zubair

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:37:42