【问题标题】:Customize underline pattern in NSAttributedString (iOS7+)在 NSAttributedString (iOS7+) 中自定义下划线模式
【发布时间】:2014-10-20 20:19:09
【问题描述】:

我正在尝试为NSAttributedString(在 iOS7+/TextKit 上)添加点下划线样式。当然,我试过内置的NSUnderlinePatternDot

NSString *string = self.label.text;
NSRange underlineRange = [string rangeOfString:@"consetetur sadipscing elitr"];

NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:string];
[attString addAttributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot)} range:underlineRange];

self.label.attributedText = attString;

但是,由此产生的样式实际上是虚线而不是点:

我是否在这里遗漏了一些明显的东西 (NSUnderlinePatternReallyDotted? ;)) 或者是否有办法自定义线点图案?

【问题讨论】:

    标签: ios nsattributedstring underline textkit


    【解决方案1】:

    根据 Eiko&Dave 的回答,i made an example like this

    我尝试使用 UILabel 而不是 UITextView 来完成,但在从 stackoverflow 或其他网站搜索后找不到解决方案。所以,我使用 UITextView 来做到这一点。

        let storage = NSTextStorage()
        let layout = UnderlineLayout()
        storage.addLayoutManager(layout)
        let container = NSTextContainer()
        container.widthTracksTextView = true
        container.lineFragmentPadding = 0
        container.maximumNumberOfLines = 2
        container.lineBreakMode = .byTruncatingTail
        layout.addTextContainer(container)
    
        let textView = UITextView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width-40, height: 50), textContainer: container)
        textView.isUserInteractionEnabled = true
        textView.isEditable = false
        textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        textView.text = "1sadasdasdasdasdsadasdfdsf"
        textView.backgroundColor = UIColor.white
    
        let rg = NSString(string: textView.text!).range(of: textView.text!)
        let attributes = [NSAttributedString.Key.underlineStyle.rawValue: 0x11,
                          NSAttributedString.Key.underlineColor: UIColor.blue.withAlphaComponent(0.2),
                          NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17),
                          NSAttributedString.Key.baselineOffset:10] as! [NSAttributedString.Key : Any]
    
        storage.addAttributes(attributes, range: rg)
        view.addSubview(textView)
    

    覆盖 LayoutManage 方法

    class UnderlineLayout: NSLayoutManager {
        override func drawUnderline(forGlyphRange glyphRange: NSRange, underlineType underlineVal: NSUnderlineStyle, baselineOffset: CGFloat, lineFragmentRect lineRect: CGRect, lineFragmentGlyphRange lineGlyphRange: NSRange, containerOrigin: CGPoint) {
        if let container = textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) {
            let boundingRect = self.boundingRect(forGlyphRange: glyphRange, in: container)
            let offsetRect = boundingRect.offsetBy(dx: containerOrigin.x, dy: containerOrigin.y)
    
            let left = offsetRect.minX
            let bottom = offsetRect.maxY-15
            let width = offsetRect.width
            let path = UIBezierPath()
            path.lineWidth = 3
            path.move(to: CGPoint(x: left, y: bottom))
            path.addLine(to: CGPoint(x: left + width, y: bottom))
            path.stroke()
        }
    }
    

    在我的解决方案中,我必须扩展 lineSpacing 并使用 NSAttributedString 属性 NSMutableParagraphStyle().lineSpacing 保留自定义下划线,但似乎没有用,但 NSAttributedString.Key.baselineOffset 有效。希望可以

    【讨论】:

      【解决方案2】:

      我花了一些时间在 Text Kit 上玩,以使这个和其他类似的场景工作。这个问题的实际 Text Kit 解决方案是继承 NSLayoutManager 并覆盖 drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:)

      这是被调用以实际绘制NSTextStorage 中的属性所请求的下划线的方法。下面是传入参数的说明:

      • glyphRange第一个字形的索引和后面要下划线的字形数
      • underlineTypeNSUnderlineStyle 属性的NSUnderlineStyle
      • baselineOffset边界框底部与所提供范围内字形基线偏移之间的距离
      • lineFragmentRect 包围包含 glyphRange 的整行的矩形
      • lineFragmentGlyphRange 构成包含 glyphRange 的整行的字形范围
      • containerOrigin容器在所属文本视图中的偏移量

      画下划线的步骤:

      1. 使用NSLayoutManager.textContainer(forGlyphAt:effectiveRange:) 查找包含glyphRangeNSTextContainer
      2. 使用NSLayoutManager.boundingRect(forGlyphRange:in:) 获取glyphRange 的边界矩形
      3. 使用CGRect.offsetBy(dx:dy:)将边界矩形偏移文本容器原点
      4. 在偏移边界矩形底部和基线之间的某处绘制自定义下划线(由baselineOffset 确定)

      【讨论】:

        【解决方案3】:

        我知道这是一个 2 年前的故事,但也许它会帮助面临同样问题的人,就像我上周所做的那样。 我的解决方法是继承 UITextView,添加一个子视图(虚线容器)并使用 textView 的 layoutManager 方法 enumerateLineFragmentsForGlyphRange 来获取行数和它们的帧数。 知道了线的框架,我计算了我想要点的y。所以我创建了一个视图(我在其中绘制点的 lineView),将clipsToBounds 设置为YES,宽度设置为 textView 的宽度,然后 在y 的linesContainer 中作为子视图添加。 之后,我将 lineView 的 width 设置为 enumerateLineFragmentsForGlyphRange 返回的值。 对于多行使用相同的方法:根据enumerateLineFragmentsForGlyphRange 返回的内容更新框架、添加新行或删除。 每次文本更新后都会在textViewDidChange 中调用此方法。 这是获取行及其框架数组的代码

        NSLayoutManager *layoutManager = [self layoutManager];
        [layoutManager enumerateLineFragmentsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) 
                                                usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {
                                                                [linesFramesArray addObject:NSStringFromCGRect(usedRect)];
                                                           }
        ];
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-12-26
          • 1970-01-01
          • 2016-04-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多