Search code examples
iphonecore-textnsattributedstring

How do I add tab stops to an NSAttributedString and display in a UITextView


I'm creating an iOS app, and I would like to display an attributed string with specific tab stops specified in a UITextView. I would also like to draw them directly into UIViews using Core Text in drawRect. I would like (if possible) for the same attributed string to be used in both scenarios.

So, in my app, I create an CFAttributedStringRef or an NSAttributedString and apply a CTParagraphStyle attribute to it. Then, I attempt to display the attributed string in a UITextView and I get a crash like the following:

-[__NSCFType headIndent]: unrecognized selector sent to instance 0x7545080

po 0x7545080
$0 = 122966144 CTParagraphStyle:
base writing direction = -1, alignment = 4, line break mode = 0, 
   default tab interval = 0
first line head indent = 0, head indent = 0, tail indent = 0
line height multiple = 0, maximum line height = 0, minimum line height = 0
line spacing adjustment = 0, paragraph spacing = 0, 
   paragraph spacing before = 0
tabs:
(
   "CTTextTab: location = 20, alignment = 0, options = (none)\n",
   "CTTextTab: location = 40, alignment = 0, options = (none)\n",
   "CTTextTab: location = 60, alignment = 0, options = (none)\n",
   "CTTextTab: location = 80, alignment = 0, options = (none)\n",
   "CTTextTab: location = 100, alignment = 0, options = (none)\n",
   "CTTextTab: location = 120, alignment = 0, options = (none)\n"
)

I understand what is going on, but I am wondering if I have any alternative way of doing what I'd like to. NSMutableParagraphStyle on iOS does not have tab stop support, yet I notice that NSMutableParagraphStyle on OS X does have this capability. This leads me to think that the iOS NSMutableParagraphStyle may one day support this.

In the meantime, is there way to add tab stops to a CFAttributedStringRef or an NSAttributedString and still have a UITextView display it?

The source in question is:

- (void)viewWillAppear:(BOOL)animated
{
   CFDictionaryRef attrs = (__bridge CFDictionaryRef) @{};
   CFAttributedStringRef a = CFAttributedStringCreate(
       kCFAllocatorDefault, CFSTR("a\tb\tc\td"), attrs);

   CFMutableAttributedStringRef attrStr;
   attrStr = CFAttributedStringCreateMutableCopy(
       kCFAllocatorDefault, CFAttributedStringGetLength(a), a);

   CFArrayRef tabStops = (__bridge CFArrayRef) @[
       (__bridge id) CTTextTabCreate(0, 20, NULL),
       (__bridge id) CTTextTabCreate(0, 40, NULL),
       (__bridge id) CTTextTabCreate(0, 60, NULL),
       (__bridge id) CTTextTabCreate(0, 80, NULL),
       (__bridge id) CTTextTabCreate(0, 100, NULL),
       (__bridge id) CTTextTabCreate(0, 120, NULL)];

   const CTParagraphStyleSetting paraSettings[] = {
       {kCTParagraphStyleSpecifierTabStops, sizeof(CFArrayRef), &tabStops},
   };

   CTParagraphStyleRef paraStyle = CTParagraphStyleCreate(
       paraSettings, 
       sizeof(paraSettings) / sizeof(CTParagraphStyleSetting));

   CFRange range = CFRangeMake(0, CFAttributedStringGetLength(attrStr));
   CFAttributedStringSetAttribute(
       attrStr, range, kCTParagraphStyleAttributeName, paraStyle);

   CFRelease(paraStyle);
   CFIndex i, count = CFArrayGetCount(tabStops);
   for (i = 0; i < count; i++) {
       CFRelease(CFArrayGetValueAtIndex(tabStops, i));
   }

   [[self textView] setAttributedText:
       (__bridge NSAttributedString *)(attrStr)];
}

Solution

  • In iOS 7 you can do it like this:

    UIFont *font = [UIFont systemFontOfSize:18.0];
    NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    NSInteger cnt;
    CGFloat tabInterval = 72.0;
    paragraphStyle.defaultTabInterval = tabInterval;
    NSMutableArray *tabs = [NSMutableArray array];
    for (cnt = 1; cnt < 13; cnt++) {    // Add 12 tab stops, at desired intervals...
        [tabs addObject:[[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:tabInterval * cnt options:nil]];
    }
    paragraphStyle.tabStops = tabs;
    NSDictionary *attributes = @{ NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle};