public
Last active

UILabel get CGRect for substring of text

  • Download Gist
UIFont+DahDit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
//
// UIFont+DahDit.m
// DahDit
//
// Created by Erik Andersson on 2011-07-28.
// Copyright 2011 Åva gymnasium. All rights reserved.
//
 
#import "UIFont+DahDit.h"
 
 
@implementation UIFont (DahDit)
 
- (CGFloat)ddLineHeight
{
if ( [self respondsToSelector:@selector(lineHeight)] )
return self.lineHeight;
return self.leading;
}
 
@end
UILabel+Fix.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
// By Erik Andersson
// Please help with this method to bring support for more UILineBreakModes and text alignment
 
@implementation UILabel (Fix)
 
- (CGRect)rectForLetterAtIndex:(NSUInteger)index
{
NSAssert(self.lineBreakMode != UILineBreakModeClip, @"UILabel.lineBreakMode cannot be UILineBreakModeClip to calculate the rect of a character. You might think that it's possible, seeing as UILineBreakModeWordWrap is supported, and they are almost the same. But the semantics are weird. Sorry.");
NSAssert(self.lineBreakMode != UILineBreakModeHeadTruncation, @"UILabel.lineBreakMode cannot be UILineBreakModeHeadTruncation to calculate the rect of a character. We can't have everything you know.");
NSAssert(self.lineBreakMode != UILineBreakModeMiddleTruncation, @"UILabel.lineBreakMode cannot be UILineBreakModeMiddleTruncation to calculate the rect of a character. We can't have everything you know.");
NSAssert(self.lineBreakMode != UILineBreakModeTailTruncation, @"UILabel.lineBreakMode cannot be UILineBreakModeTailTruncation to calculate the rect of a character. We can't have everything you know.");
// Check if label is empty. Should add so it also checks for strings containing only spaces
if ( [self.text length] == 0 )
{
return self.bounds;
}
// Algorithm goes like this:
// 1. Determine which line the letter is on
// 2. Get the x-position on the line by: width of string up to letter
// 3. Apply UITextAlignment to the x-position
// 4. Add y position based on height of letters * line number
// Et víolà!
NSString *letter = [self.text substringWithRange:NSMakeRange(index, 1)];
// Determine which line the letter is on and the string on that line
CGSize letterSize = [letter sizeWithFont:self.font];
int lineNo = 0;
int linesDisplayed = 1;
// Get the substring with the line on it
NSUInteger lineStartsOn = 0;
NSUInteger currentLineLength = 1;
// Temporary variables
NSUInteger currentLineStartsOn = 0;
NSUInteger currentCurrentLineLength = 1;
float currentWidth;
// TODO: Add support for UILineBreakModeWordWrap, UILineBreakModeCharacterWrap to complete implementation
// Get the line number of the current letter
// Get the contents of that line
// Get the total number of lines (which means that no matter what we loop through the entire thing)
BOOL isDoneWithLine = NO;
NSUInteger i = 0, len = [self.text length];
// The loop is different depending on the lineBreakMode. If it is UILineBreakModeCharacterWrap it is easy
// just check for every single character. For UILineBreakModeWordWrap it is a bit more tedious. We have
// to think in terms of words. We have to find each word and check it. If it is longer than the frame width
// then we know we have a new word, and that lines index starts on the words beginning index.
// Spaces prove to be even morse troublesome. Several spaces in a row at the end of a line won't result in
// any more width.
for ( ; i < len; i++ )
{
NSString *currentLine = [self.text substringWithRange:NSMakeRange(currentLineStartsOn, currentCurrentLineLength)];
CGSize currentSize = [currentLine sizeWithFont:self.font constrainedToSize:CGSizeMake(self.frame.size.width, 1000) lineBreakMode:self.lineBreakMode];
currentWidth = currentSize.width;
if ( currentSize.height > self.font.ddLineHeight )
{
// We have to go to a new line
linesDisplayed++;
//NSLog(@"new line on: %d", i);
// If i <= index that means we are on the correct letter's line
// store that
if ( i <= index )
{
lineStartsOn = i;
lineNo++;
currentLineLength = 1;
}
else
isDoneWithLine = YES;
currentLineStartsOn = i;
currentCurrentLineLength = 1;
i--;
}
else
{
// Okay with the same line
currentCurrentLineLength++;
if ( ! isDoneWithLine )
{
currentLineLength++;
}
}
}
// Make sure we didn't overstep the bounds
while ( lineStartsOn + currentLineLength > len )
currentLineLength--;
// Check if linesDisplayed is faulty, if for example lines have been clipped
CGSize totalSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(self.frame.size.width, 100000) lineBreakMode:self.lineBreakMode];
if ( totalSize.height > self.frame.size.height )
{
// It has been clipped, calculate how many lines are actually shown
linesDisplayed = 0;
float ddLineHeight = 0;
while ( ddLineHeight < self.frame.size.height )
{
ddLineHeight += self.font.ddLineHeight;
linesDisplayed++;
}
linesDisplayed--;
// Number of lines is not automatic, keep it within that range
if ( self.numberOfLines > 0 )
linesDisplayed = linesDisplayed > self.numberOfLines ? self.numberOfLines : linesDisplayed;
}
// Length of the substring up and including this letter
NSUInteger currentLineSubstrLength = index - lineStartsOn + 1;
currentWidth = [[self.text substringWithRange:NSMakeRange(lineStartsOn, currentLineLength)] sizeWithFont:self.font].width;
NSString *lineSubstr = [self.text substringWithRange:NSMakeRange(lineStartsOn, currentLineSubstrLength)];
float x = [lineSubstr sizeWithFont:self.font].width - [letter sizeWithFont:self.font].width;
float y = self.frame.size.height/2 - (linesDisplayed*self.font.ddLineHeight)/2 + self.font.ddLineHeight*lineNo;
if ( self.textAlignment == UITextAlignmentCenter )
{
x = x + (self.frame.size.width-currentWidth)/2;
}
else if ( self.textAlignment == UITextAlignmentRight )
{
x = self.frame.size.width-(currentWidth-x);
}
return CGRectMake(x, y, letterSize.width, letterSize.height);
}
 
@end

Have you managed to progress this at all?

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.