Skip to content

Rendered NSAttributedString boundingRect result incorrect #310

@dourgulf

Description

@dourgulf

Please help prevent duplicate issues before submitting a new one:

  • [*] I've searched other open/closed issues for duplicates before opening up this new issue.

Report

What did you do?

When rendering text with many "\n" inside, the result of bundingRect of NSAttributedString is incorrect.
Reproduced code:

import Down
import Foundation

@objcMembers
class OwllMarkdownStyler: NSObject {
    static func test() {
        let content = "根据文章中的内容,要辨别具有高影响力的早期接纳者,可以按照以下步骤进行:\n\n---\n\n### 1. **定义早期接纳者的关键特征**\n早期接纳者是那些对问题感受强烈、愿意尝试新方法解决问题的用户。他们与主流客户群体不同,通常更开放、更有风险偏好,同时对创新更感兴趣。\n\n**文章具体提到的辨别方式:**\n- **受问题困扰最严重的人**:\n   早期接纳者是那些对你要解决的问题感到强烈痛苦或需求的人。例如,在CloudFire的案例中,最终锁定的早期接纳者是“首次生育且孩子年龄在三岁以下的母亲”,因为这个群体对分享孩子照片和视频有最强烈的需求。\n- **愿意尝试你的解决方案**:\n   早期接纳者不仅仅是关注问题,还愿意为解决问题尝试你的最简可行产品(MVP)或解决方案。\n\n---\n\n### 2. **用以下问题筛选早期接纳者**\n在用户访谈过程中,可以针对客户的行为和需求深入挖掘,筛选潜在的早期接纳者。这些问题可以包括:\n- 他们是否正在积极寻找现有解决方案? \n- 他们现在使用了哪些解决方案?是否对现用方案感到不满?\n- 如果有更有效的解决方案,他们是否愿意尝试或付出代价?\n\n> 如果用户对问题的描述非常详细,且目前解决问题的方法繁琐或昂贵,那么他们可能是有高需求的早期接纳者。\n\n---\n\n### 3. **筛选方法:观察行为而非仅凭语言**\n一些用户可能会在访谈中表示问题重要,但是并没有实际行动。辨别真正的早期接纳者时:\n- 看他们是否已经在尝试用现有工具解决这个问题。\n- 如果现有工具无法解决问题,他们是否愿意投入时间和金钱寻找新的办法。\n  \n例如,在访谈中,如果客户声称“非常需要解决方案”,但并未主动尝试任何方法,那么他的需求可能并不是刚需。\n\n---\n\n### 4. **验证早期接纳者的行动**\n一个重要的方法是让客户付出一定的成本(例如金钱或时间):\n- **口头承诺不足以作为判断依据**。例如,客户可以口头表示对你的解决方案感兴趣,但更准确的方式是看看他们是否愿意预付费用、注册试用,或者至少愿意花时间来帮助你测试。\n\n---\n\n### 5. **利用访谈和聚焦特定人群**\n- **从用户访谈中获得反馈**:通过一次次访谈,从“最有需求的客户”中不断缩小范围,例如从广泛的客户群体中找到某一特定场景下需求最强烈的群体。\n- **建立心理和行为模型**:根据访谈结果深度细化对早期接纳者的定义。例如,文章中提到家长是目标用户,但通过访谈就进一步定义为\"首次生育的年轻母亲\"\n\n---\n\n### 6. **识别哪些人有影响力**\n高影响力的早期接纳者通常具备以下特性:\n- 他们的话语能影响社区圈层,例如拥有与你目标市场相关的线上社交平台或线下资源。\n- 他们对拟解决的问题有一定的专业性或经验。\n  \n例如,如果你的目标客户是家长,那么具有社交媒体账号并经常分享亲子内容的早期母亲,可能是影响力更高的早期接纳者。\n\n---\n\n### 总结\n辨别高影响力的早期接纳者需要从用户访谈入手,通过观察用户对痛点问题的态度、当前行为,以及他们是否愿意主动测试或支付成本来精准识别。通过一次次反馈和调研,不断缩窄用户群体范围,从而找到真正可以传播影响、并推动产品发展的早期接纳者。"
        let attributedString = self.testAttributedString(content)!
        let size = CGSize(width: 320, height: 100000)
        let result1 = attributedString.boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
        print("result1:\(result1.size)")
//        result1:(319.86933061770435, 2528.055078125)
        
        
        // the really display size can be calulated by UILabel correctyly
        let label = UILabel()
        label.numberOfLines = 0
        label.attributedText = attributedString
        let result2 = label.sizeThatFits(size)
        print("result2:\(result2)")
//        result2:(320.0, 2630.3333333333335)
    }
    
    static func testAttributedString(_ content: String) -> NSAttributedString? {
        let down = Down(markdownString: content)
        return try? down.toAttributedString(styler: DownStyler())
    }
}

I am not sure this is the problem of boundingRect function or the Down's problem?

What did you expect to happen?

result is correct.

What happened instead?

result size is less than really display size

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions