frankWhite
frankWhite

Reputation: 1542

ios method CTFrameGetLines return empty array

I have NSAttributedString like (only one symbol - s and attributes):

s{
    CTForegroundColor = "<CGColor 0x8878330> [<CGColorSpace 0x717da80> (kCGColorSpaceDeviceGray)] ( 0 0 )";
CTRunDelegate = "<CTRunDelegate 0x8878270 [0x16e14d8]>";
NSFont = "CTFont <name: HelveticaNeue-Light, size: 15.000000, matrix: 0x0>\nCTFontDescriptor <attributes: <CFBasicHash 0x88720f0 [0x16e14d8]>{type = mutable dict, count = 1,\nentries =>\n\t1 : <CFString 0x2a9838 [0x16e14d8]>{contents = \"NSFontNameAttribute\"} = <CFString 0x886f5f0 [0x16e14d8]>{contents = \"HelveticaNeue-Light\"}\n}\n>";

I creaate framesseter and frame (they are not nil) and want get lines:

NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);

but this array is empty. I don't understand why.
Thanks in advance.

UPD: my frame is

CTFrame: visible string range = (0, 0)<CGPath 0x75a63f0><CFArray 0x759a5d0 [0x16e14d8]>{type = mutable-small, count = 0, values = ()}

seems like core text don't see attributed string

UPD: Added code of run delegate

    CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)kSBTextFontAttribute, kSBFontSize, NULL);
    CTRunDelegateCallbacks callbacks;
    callbacks.version = kCTRunDelegateVersion1;
    callbacks.getAscent = ascentCallback;
    callbacks.getDescent = descentCallback;
    callbacks.getWidth = widthCallback;
    callbacks.dealloc = 0;

 NSDictionary *imgAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                             @kSmileWidth, kSBCallbackWidth,
                             @kSmileHeight, kSBCallbackHeight,
                             nil];

    CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, CFBridgingRetain(imgAttributes));
    NSDictionary *attrDictionaryDelegate = [NSDictionary dictionaryWithObjectsAndKeys:
                                            //set the delegate
                                            (__bridge id)delegate, (NSString*)kCTRunDelegateAttributeName,
                                            (id)kSmileFontColor, kCTForegroundColorAttributeName,
                                            (__bridge id)fontRef,       kCTFontAttributeName,
                                            nil];

     /////////

     static void deallocCallback( void* ref ){
      ref = nil;
    }
    static CGFloat ascentCallback( void *ref ){
      return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:kSBCallbackHeight]  floatValue];
     }
     static CGFloat descentCallback( void *ref ){
        return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:kSBCallbackDescending] floatValue];
     }
     static CGFloat widthCallback( void* ref ){
         return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:kSBCallbackWidth] floatValue];
     }

Upvotes: 3

Views: 1577

Answers (1)

Rob Napier
Rob Napier

Reputation: 299355

Since your CTFrame is empty ("visible string range = (0,0)"), that indicates that the framesetter couldn't lay out any text. There are several of reason that could be true:

  • In CTFramesetterCreateWithAttributedString, you may have passed an empty attributed string. It is possible that you are not passing the object you think you are, or that the object is created later than the framesetter. Check with the debugger.

  • In CTFramesetterCreateFrame, you may be passing a path that is too small to contain any text. I usually like to draw my path my drawRect: so that I can see if it's the shape I think it should be. For a very simple path, you may be able to figure out its shape in the debugger.

  • Somewhat less likely, but possible, in CTFramesetterCreateFrame, you may be passing a range that is at the very end of the string. Again, the debugger is the obvious tool here.


You need to keep breaking down this problem until you find the simplest case that fails. I'm seeing no problem with the code you've actually published (except for your memory management error; you need to CFRelease() the dictionary when you deallocate). The following full example works fine for me:

NSString * const kSBCallbackHeight = @"kSBCallbackHeight";
NSString * const kSBCallbackDescending = @"kSBCallbackDescending";
NSString * const kSBCallbackWidth = @"kSBCallbackWidth";

const CGFloat kSmileWidth = 10.0;
const CGFloat kSmileHeight = 10.0;

static void deallocCallback( void* ref ){
  CFRelease(ref); // FIXED
}

static CGFloat ascentCallback( void *ref ){
  return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:kSBCallbackHeight]  floatValue];
}
static CGFloat descentCallback( void *ref ){
  return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:kSBCallbackDescending] floatValue];
}
static CGFloat widthCallback( void* ref ){
  return [(NSString*)[(__bridge NSDictionary*)ref objectForKey:kSBCallbackWidth] floatValue];
}

void testDelegate()
{
  CTRunDelegateCallbacks callbacks;
  callbacks.version = kCTRunDelegateVersion1;
  callbacks.getAscent = ascentCallback;
  callbacks.getDescent = descentCallback;
  callbacks.getWidth = widthCallback;
  callbacks.dealloc = deallocCallback;  // FIXED

  NSDictionary *imgAttributes = @{ kSBCallbackWidth: @(kSmileWidth),  // Switched to const rather than #define
                                   kSBCallbackHeight: @(kSmileHeight)
                                  };

  CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (void*)CFBridgingRetain(imgAttributes));
  NSDictionary *attrDictionaryDelegate = @{ (id)kCTRunDelegateAttributeName: (__bridge id)delegate };

  NSAttributedString *as = [[NSAttributedString alloc] initWithString:@"s" attributes:attrDictionaryDelegate];
  CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFTypeRef)as);
  CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
                                              CFRangeMake(0, 0),
                                              [[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)] CGPath],
                                              NULL);
  NSArray *lines = (__bridge id)CTFrameGetLines(frame);

  NSLog(@"%@", lines);
}

Upvotes: 3

Related Questions