在UITextView中画一条线 - NSAttributedString

8

我希望在一个 UITextView 中绘制一条可定制的线,该文本由一些文字组成(使用 NSAttributedString

以下是我的尝试:

NSString *unicodeStr = [NSString stringWithFormat:@"%C%C%C", 0x00A0, 0x0009, 0x00A0]; //nbsp, tab, nbsp
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:unicodeStr];
NSRange strRange = NSMakeRange(0, str.length);

NSMutableParagraphStyle *const tabStyle = [[NSMutableParagraphStyle alloc] init];
tabStyle.headIndent = 16; //padding on left and right edges
tabStyle.firstLineHeadIndent = 16;
tabStyle.tailIndent = -16;
NSTextTab *listTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentCenter location:40 options:@{}]; //this is how long I want the line to be
tabStyle.tabStops = @[listTab];
[str  addAttribute:NSParagraphStyleAttributeName value:tabStyle range:strRange];
[str addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInt:2] range:strRange];

无论我在制表符位置(此处为40)和尾部缩进(此处为-16)提供什么值,该行只会遵守头部缩进并跨越整个UITextView的宽度(当然除了头部缩进)。

编辑 - 我非常确定问题是因为我没有使用正确的Unicode字符(尽管它们似乎是合理的选择)。如果我在第二个nbsp之后即朝向结尾添加一个空格,则制表符限于单个制表符长度。

你能提供一个类似于预期文本的示例吗? - Danny Bravo
我正在尝试实现一个 hr 标签(水平线)。在从 HTML 转换时,NSAttributedString 不支持它们。 - lostInTransit
3个回答

2

这是您期望的结果吗?

输入图像描述 输入图像描述

您可以尝试这个:

NSTextTab *listTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentCenter location:self.textView.frame.size.width - tabStyle.firstLineHeadIndent + tabStyle.tailIndent options:@{}];

以下是完整代码:

- (void) viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  NSString *unicodeStr = @"\n\u00a0\t\t\n";
  NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:unicodeStr];
  NSRange strRange = NSMakeRange(0, str.length);

  NSMutableParagraphStyle *const tabStyle = [[NSMutableParagraphStyle alloc] init];
  tabStyle.headIndent = 16; //padding on left and right edges
  tabStyle.firstLineHeadIndent = 16;
  tabStyle.tailIndent = -70;
  NSTextTab *listTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentCenter location:self.textView.frame.size.width - tabStyle.headIndent + tabStyle.tailIndent options:@{}]; //this is how long I want the line to be
  tabStyle.tabStops = @[listTab];
  [str  addAttribute:NSParagraphStyleAttributeName value:tabStyle range:strRange];
  [str addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInt:2] range:strRange];

  NSAttributedString *htmlStr = [[NSAttributedString alloc] initWithData:[@"<h1>Lorem ipsum dolor sit er elit lamet</h1>" dataUsingEncoding:NSUnicodeStringEncoding] options:@{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType } documentAttributes:nil error:nil];

  [str insertAttributedString:htmlStr atIndex:0];
  [str appendAttributedString:htmlStr];

  self.textView.attributedText = str;
}

已经尝试过了,但没有起作用。我相当确定这与tab两侧的nbsp有关,但我无法弄清原因。 - lostInTransit
这是我想要的吗?是的。但是我有一些文本在前面和后面(HTML),似乎会搞砸水平线(我猜?)。代码似乎与我所拥有的并没有太大区别,不确定为什么对我而言不能正常工作。 - lostInTransit
为什么在尺寸没有被计算出来的情况下,您会传递 'self.textView.frame.size.width' 的引用? - famfamfam

1

这是我解决同样问题的方法。它使用了 NSTextTab 的子类:

import UIKit

class RightAnchoredTextTab : NSTextTab {
    weak var textContainer : NSTextContainer!

    required init(textAlignment alignment: NSTextAlignment, location loc: CGFloat, options: [String : AnyObject], textContainer aTextContainer : NSTextContainer) {
        super.init(textAlignment: alignment, location: loc, options: options)
        textContainer = aTextContainer
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override var location: CGFloat {
        get {
           return textContainer.size.width-textContainer.lineFragmentPadding*2-super.location
        }
    }
}

和其他解决方案类似的类似部分:

func horizontalLine(indent : CGFloat = 30, width : CGFloat = 1) -> NSAttributedString
{
    let paragraphStyle = NSMutableParagraphStyle()

    paragraphStyle.tabStops = []
    paragraphStyle.addTabStop(NSTextTab(textAlignment: .Left, location: indent, options: [:]))
    paragraphStyle.addTabStop(RightAnchoredTextTab(textAlignment: .Right, location: indent, options: [:], textContainer : textView.textContainer))
    paragraphStyle.alignment = .Left

    let attributes = [NSParagraphStyleAttributeName : paragraphStyle, NSStrikethroughStyleAttributeName : width]

    textView.backgroundColor = UIColor.yellowColor()
    let ZeroWidthNonBreakingSpace = "\u{FEFF}"
    return  NSAttributedString(string: "\t\(ZeroWidthNonBreakingSpace)\t\(ZeroWidthNonBreakingSpace)\n", attributes:  attributes)
}

几点注意事项:

  • 它可能只适用于单个正方形文本容器。
  • NSTextContainer 有一个 lineFragmentPadding。如果你曾经想知道为什么必须将右对齐制表符缩进10个字符,那就是默认值为5的原因。
  • 在文本前加上 \n 前缀意味着没有删除线。不知道为什么。
  • 此解决方案可与旋转等一起使用,因为 UITextView 在更改边界时再次调用 NSTextTab.location

1
它的工作效果非常好...我有6个段落,必须在每个段落的末尾添加该行,上述解决方案完美地解决了这个问题。我只做了一个更改,因为我需要文本视图大小的完整分隔线,所以我修改了这一行 - "return textContainer.size.width-textContainer.lineFragmentPadding*2",然后我得到了我想要的结果。谢谢!! - Dishant

0

对于 Swift 5,

    let unicodeStr = "\n\u{00a0}\t\t\n"
    let str = NSMutableAttributedString(string: unicodeStr)
    let strRange = NSRange(location: 0, length: str.length)

    let tabStyle = NSMutableParagraphStyle()
    tabStyle.headIndent = 0 //padding on left and right edges
    tabStyle.firstLineHeadIndent = 0
    //tabStyle.tailIndent = -16
    let listTab = NSTextTab(textAlignment: .center, location: textView.frame.size.width-10, options: [:]) //this is how long I want the line to be
    tabStyle.tabStops = [listTab]
    str.addAttribute(.paragraphStyle, value: tabStyle, range: strRange)
    str.addAttribute(.strikethroughStyle, value: NSNumber(value: 2), range: strRange)
    str.addAttribute(.strikethroughColor, value: UIColor.label, range: strRange)
    str.addAttribute(NSAttributedString.Key.font, value: self.currentFont, range: strRange)
    str.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.label, range: strRange)


    textView.textStorage.append(str)

    //To set cursor end of document
    let endPosition = self.textView.endOfDocument
    self.textView.selectedTextRange = self.textView.textRange(from: endPosition, to: endPosition)

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