在NSAttributedString(iOS7+)中自定义下划线模式

7

我试图为iOS7+/TextKit中的NSAttributedString添加点线下划线样式。当然,我已经尝试使用内置的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;

然而,这种方式产生的样式实际上更像是虚线而不是点线:

screenshot

我是否遗漏了一些明显的东西(NSUnderlinePatternReallyDotted?;))或者有没有一种方法可以自定义线点模式呢?

3个回答

4
根据Eiko&Dave的回答,我做了一个类似于这样的例子
我试着用UILabel来完成,但在Stack Overflow或其他网站上搜索后都找不到解决方法。因此,我使用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()
    }
}

在我的解决方案中,我需要通过使用NSAttributedString属性NSMutableParagraphStyle().lineSpacing来扩展行间距,并保留自定义下划线,但似乎这并没有起作用,但是NSAttributedString.Key.baselineOffset是有效的。希望能解决这个问题。

3

我花了一点时间来尝试使用Text Kit使这个和其他类似的场景工作。实际上,Text Kit解决这个问题的方法是子类化NSLayoutManager并覆盖drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:)

这个方法被调用来绘制由NSTextStorage中的属性请求的下划线。以下是传递的参数的描述:

  • glyphRange 第一个字形的索引和要加下划线的后续字形数目
  • underlineType NSUnderlineAttributeName属性的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确定)之间的某个位置绘制自定义下划线。

0

我知道这是一个两年前的故事,但也许它会帮助到遇到相同问题的人,就像上周我一样。我的解决方法是继承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)];
                                                   }
];

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