Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw a line inside a UITextView - NSAttributedString

I wish to draw a customizable line inside a UITextView consisting of some text (using NSAttributedString)

Here's what I tried

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];

But no matter what value I provide for the tab stop location (40 in this case) and tailIndent (-16 here), the line only respects the headIndent and spans the entire UITextView width (minus the headIndent of course).

EDIT - I am pretty sure the issue is because I am not using the correct unicode chars (although they seem to be the logical choice). In case this gives someone a hint, if I add a space after the 2nd nbsp i.e. towards the end, the tab is limited to a single tab length

like image 244
lostInTransit Avatar asked Mar 11 '16 14:03

lostInTransit


2 Answers

Is it your expected result?

enter image description here enter image description here

Can you try this:

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

And this is the full code:

- (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;
}
like image 77
Buu Bui Avatar answered Oct 16 '22 11:10

Buu Bui


Here's what I did to solve the same issue. It uses a subclass of 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
        }
    }
}

And similar bits similar to other solutions:

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)
}

A few notes:

  • It will probably only work with a single square text container.
  • NSTextContainer has a lineFragmentPadding. If you've every wondered why you have to indent a right alignment tab by 10 this is it: the default value is 5.
  • prefixing the text with \n meant there was no strikethrough. No idea why.
  • This solution works with rotations etc as UITextView calls NSTextTab.location again when the bounds are changed.
like image 28
jjrscott Avatar answered Oct 16 '22 09:10

jjrscott