iPhone: break the text into line for frame - iphone

I have a looong text and a UILabel with width 300 with wordwrap mode. I need do know how many lines will be if to set my text to this label...And the main thing is to get line where the world "hello" is. How to do that ? How to break text into lines for some frame with some width and get the array of this lines ? Thanks....

Well, to get the number of lines, all you have to do is take your string and use sizeWithFont:constrainedToSize: then divide the height by your label's UIFont's lineHeight property.
As for getting individual lines, I'm not sure if there's a way to do this with Objective-C, so you might have to use Core Text.
Create an NSAttributedString from your string.
Set the font.
Create a CTFrameSetter from the NSAttributedString
Create the CTFrame
Get a CFArrayRef of lines from CTFrame using CTFrameGetLines
Enumerate through the array and find your word.
If you use fast enumeration, then you'll need a counter to keep track of the line number.
Example of the line breaking part:
CTFontRef myFont = CTFontCreateWithName([font fontName], [font pointSize], NULL);
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:string];
[attStr addAttribute:(NSString *)kCTFontAttributeName value:(id)myFont range:NSMakeRange(0, attStr.length)];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attStr);
CGPathRef path = [[UIBezierPath bezierPathWithRect:textFrame] CGPathRef];
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
NSArray *lines = (NSArray *)CTFrameGetLines(frame);
You'll have to add the appropriate releases depending on whether you're using ARC or not.
Note that the variable "font" is a UIFont. If you want to create a font, you don't have to use those fontName and pointSize methods.
Also note: this is an array of CTLineRef objects. Use CTLineGetStringRange to get the CFRange of the entire line. Then use that to create a substring from your NSString to do your searching in.
NSUInteger idx = 0;
for (CTLineRef line in lines) {
CFRange lineRange = CTLineGetStringRange(line);
NSRange range = NSMakeRange(lineRange.location, lineRange.length);
NSString *lineString = [string substringWithRange:range];
NSRange searchResultRange = [lineString rangeOfString:#"hello"];
if (searchResultRange.location != NSNotFound) {
// MATCH!
break;
}
idx++;
}

Related

How to get the range where UILabel will truncate a text [duplicate]

This question already has answers here:
Get truncated text from UILabel
(3 answers)
Closed 9 years ago.
I'm trying to figure out a way to understand at which range an instance of UILabel will truncate a text. I know how to get the size that a string would occupy using the -sizeWithFont:constrainedToSize:lineBreakMode:.
Let's say that we have a UILabel of about 5 lines and a long text, using the method above I'm able to know if it will fit or not. If it doesn't fit I'd like to add another UILabel with the remaining text. I'm doing that because the view layout is mixed with an image and when the image finish I'd like to have a text along the whole width of the view.
I know that with core text I can do that in just one view, but I'd prefer to go easy with UILabel.
/*IMAGE*/##/*TEXT*/
/*IMAGE*/##/*TEXT*/
/*IMAGE*/##/*TEXT*/
/*IMAGE*/##/*TEXT*/
/*IMAGE*/##/*TEXT*/
/*****TEXT*************/
/******TEXT*************/
/******TEXT**************/
Well I've found a solution the answer is a duplicate Get truncated text from UILabel
I copy the modified method from that answer, you need to import CoreText framework and be sure that the label is set to word wrap:
- (NSArray *)truncate:(NSString *)text forLabel: (UILabel*) label
{
NSMutableArray *textChunks = [[NSMutableArray alloc] init];
NSString *chunk = [[NSString alloc] init];
NSMutableAttributedString *attrString = nil;
UIFont *uiFont = label.font;
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)uiFont.fontName, uiFont.pointSize, NULL);
NSDictionary *attr = [NSDictionary dictionaryWithObject:(__bridge id)ctFont forKey:(id)kCTFontAttributeName];
attrString = [[NSMutableAttributedString alloc] initWithString:text attributes:attr];
CTFramesetterRef frameSetter;
CFRange fitRange;
while (attrString.length>0) {
frameSetter = CTFramesetterCreateWithAttributedString ((__bridge CFAttributedStringRef) attrString);
CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0,0), NULL, CGSizeMake(label.bounds.size.width, label.bounds.size.height), &fitRange);
CFRelease(frameSetter);
chunk = [[attrString attributedSubstringFromRange:NSMakeRange(0, fitRange.length)] string];
[textChunks addObject:chunk];
[attrString setAttributedString: [attrString attributedSubstringFromRange:NSMakeRange(fitRange.length, attrString.string.length-fitRange.length)]];
}
return textChunks;
}

Know where a UILabel splits a string when its multi-line property is on

Have a problem, need to assign some text to a UIButton's title. Have set the buttons line break mode to NSLineBreakByCharWrapping so that the string is separated only by characters at end of each line. But i need to insert an hyphen at the end of the line to show continuity of the word.
Heres what i tried -
// Initialize the button
titleButton.titleLabel.lineBreakMode = NSLineBreakByCharWrapping;
titleButton.titleLabel.backgroundColor = [UIColor yellowColor];
[titleButton.titleLabel setFont:[UIFont fontWithName:#"Helvetica-Bold" size:15]];
// Make the string here
NSMutableString *titleString = [[NSMutableString alloc] initWithString:#"abcdefghijklmnopqrs tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"];
// Insert hyphens
int titleLength = [titleString length];
int hyphenIndex = 19;
while (hyphenIndex<titleLength) {
UniChar charatIndex = [titleString characterAtIndex:hyphenIndex];
if ((charatIndex - ' ') != 0) { // Check if its not a break bw two words
[titleString insertString:#"-" atIndex:hyphenIndex]; // else insert an hyphen to indicate word continuity
}
hyphenIndex += 19; //Since exactly 20 char are shown in single line of the button's label.
}
//Set the hyphenated title for the button
[titleButton setTitle:titleString forState:UIControlStateNormal];
[titleString release];
This is the closest i could get.
Any help would be greatly appreciated.
Try NSAttributedString
It give a great possibilities for working with strings.
WWDC 2012-230
For example:
- (NSUInteger)lineBreakBeforeIndex:(NSUInteger)index withinRange:(NSRange)aRange
//Returns the appropriate line break when the character at the index won’t fit on the same line as the character at the beginning of the range.
your second word's length is more the length of line (width of button) thats why this happen.
pasting hyphens is not very good idea. You should split your string on parts with size that fits button's width by using than add hyphens if needed
[String sizeWithFont:font constrainedToSize:Size lineBreakMode:LineBreakMode]
The main idea is to get portion of initial string (add hyphen if word breaks), check if its width fits button's width. If width is smaller, try bigger part, else if fits -- process further part
See my answer here for how to use NSParagraphStyle and NSAttributedString to get a UILabel that breaks with hyphens: https://stackoverflow.com/a/19414663/196358

How do I truncate a string within a string in a UILabel?

Say I have The Dark Knight Rises at 7:45pm and I need to fit that into a fixed-width UILabel (for iPhone). How would I make that truncate as "The Dark Knight Ris... at 7:45pm" rather than "The Dark Knight Rises at 7:4..."?
UILabel has this property:
#property(nonatomic) NSLineBreakMode lineBreakMode;
You enable that behaviour by setting it to NSLineBreakByTruncatingMiddle.
EDIT
I din't understand that you wanted to truncate only a part of the string.Then read this:
If you want to apply the line break mode to only a portion of the text, create a new attributed string with the desired style information and associate it with the label. If you are not using styled text, this property applies to the entire text string in the text property.
Example
So there is even a class for setting the paragraph style: NSParagraphStyle and it has also it's mutable version.
So let's say that you have a range where you want to apply that attribute:
NSRange range=NSMakeRange(i,j);
You have to create a NSMutableParagraphStyle object and set it's lineBreakMode to NSLineBreakByTruncatingMiddle.Notice that you may set also a lot of other parameters.So let's do that:
NSMutableParagraphStyle* style= [NSMutableParagraphStyle new];
style.lineBreakMode= NSLineBreakByTruncatingMiddle;
Then add that attribute for the attributedText of the label in that range.The attributedText property is a NSAttributedString, and not a NSMutableAttributedString, so you'll have to create a NSMutableAttributedString and assign it to that property:
NSMutableAttributedString* str=[[NSMutableAttributedString alloc]initWithString: self.label.text];
[str addAttribute: NSParagraphStyleAttributeName value: style range: range];
self.label.attributedText= str;
Notice that there are a lot of other properties for a NSAttributedString, check here.
You have to set the lineBreakMode. You can either do that from Interface Builder or programmatically as follows
label.lineBreakMode = NSLineBreakByTruncatingMiddle;
please note that since iOS 5 the type of such property changed from UILineBreakMode to NSLineBreakMode.
My first idea would be two labels side-by-side both with fixed width, but I'll assume you've ruled that out for some unstated reason. Alternatively, compute the truncation manually, like this ...
- (NSString *)truncatedStringFrom:(NSString *)string toFit:(UILabel *)label
atPixel:(CGFloat)pixel atPhrase:(NSString *)substring {
// truncate the part of string before substring until it fits pixel
// width in label
NSArray *components = [string componentsSeparatedByString:substring];
NSString *firstComponent = [components objectAtIndex:0];
CGSize size = [firstComponent sizeWithFont:label.font];
NSString *truncatedFirstComponent = firstComponent;
while (size.width > pixel) {
firstComponent = [firstComponent substringToIndex:[firstComponent length] - 1];
truncatedFirstComponent = [firstComponent stringByAppendingString:#"..."];
size = [truncatedFirstComponent sizeWithFont:label.font];
}
NSArray *newComponents = [NSArray arrayWithObjects:truncatedFirstComponent, [components lastObject], nil];
return [newComponents componentsJoinedByString:substring];
}
Call it like this:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 160, 21)];
NSString *string = #"The Dark Knight Rises at 7:45pm";
NSString *substring = #"at";
CGFloat pix = 120.0;
NSString *result = [self truncatedStringFrom:string toFit:label atPixel:120.0 atPhrase:#"at"];
label.text = result;
This generates: #"The Dark Kni...at 7:45pm"

Get truncated text from UILabel

Is there anyway I can get the truncated version of the text for a UILabel?
In short, I have a paragraph of text, and two UILabels - label A, which is 2 lines long, and label B, which is a variable height. Label A is above label B. The idea is that label A shows the first two lines of the paragraph of text, and upon a certain user action, label B because visible and displays the rest of the text.
I'm having trouble determining what should go in label B, as I don't know what's being shown in label A. I'd need to also remove the "..." from label A.
Note: I realize this is a bit convoluted but there are some good reasons for it, which I won't clutter up the question with.
I wonder if you could use the methods in the NSString UIKit Additions to figure out how much fits into label A.
A crude way might be to start with the first character of your text and test for the size it would take up (-sizeWithFont:forWidth:lineBreakMode: maybe?) and then keep adding characters one at a time until it doesn't fit into your label A any more.
I hope somebody else can come up with a better way to do this, but the above should work.
Update
Last night I looked a bit into Core Text for my own app and came across CTFramesetterSuggestFrameSizeWithConstraints. You could maybe use this to figure out how much of your string fits into the label, by looking at the fitRange in that function.
Update 2:
I think this should work, but I have just typed this in here, so it may not even compile:
UIFont *uiFont = [UIFont systemFontOfZise:13.0f]; // whichever font you're using
CTFontRef ctFont = CTFontCreateWithName((CFStringRef)uiFont.fontName, uiFont.pointSize, NULL);
NSDictionary *attr = [NSDictionary dictionaryWithObject:(id)ctFont forKey:(id)kCTFontAttributeName];
CFRelease(ctfont);
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:yourLabelText attributes:attr];
CTFrameSetterRef frameSetter = CTFrameSetterCreateWithAttributedString((CFAttributedStringRef)attrString);
[attrString release];
CFRange fitRange;
CTFrameSetterSuggestFrameSizeWithConstrains(
frameSetter,
CFRangeMake(0, 0),
NULL,
CGSizeMake(labelWidth, labelHeight),
&fitRange);
CFRelease(frameSetter);
CFIndex numberOfCharactersThatFit = fitRange.length;
thanks to Thomas Müller
be sure to set line break mode the myLabel to this:
myLabel.lineBreakMode = NSLineBreakByWordWrapping;
by this method you can get chunked strings that actually fit in the constrained size.
Here is the baked code:
- (NSArray *)truncate:(NSString *)text
{
NSMutableArray *textChunks = [[NSMutableArray alloc] init];
NSString *chunk = [[NSString alloc] init];
CTFramesetterRef frameSetter;
UIFont *uiFont = [UIFont systemFontOfSize:17.0f];
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)uiFont.fontName, uiFont.pointSize, NULL);
NSDictionary *attr = [NSDictionary dictionaryWithObject:(__bridge id)ctFont forKey:(id)kCTFontAttributeName];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:text attributes:attr];
CFRange fitRange;
while (attrString.length>0) {
frameSetter = CTFramesetterCreateWithAttributedString ((__bridge CFAttributedStringRef) attrString);
CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0,0), NULL, CGSizeMake(myLabel.frame.size.width, myLabel.frame.size.height), &fitRange);
CFRelease(frameSetter);
chunk = [[attrString attributedSubstringFromRange:NSMakeRange(0, fitRange.length)] string];
[textChunks addObject:chunk];
[attrString setAttributedString: [attrString attributedSubstringFromRange:NSMakeRange(fitRange.length, attrString.string.length-fitRange.length)]];
}
return textChunks;
}
For Label A, calculate approximate character that should fit perfectly for two lines, for the particular font you are using.
For label B, set variable Height that the whole text must fit into it.

How to know the displayed text in UILabel?

I have an UIView containing two UILabels, in order to display a string.
The first UILabel has a fixed size, and if the string is too long and can't hold in this UILabel, I want to display the maximum characters I can in the first UILabel, and display the rest of the string in the second UILabel.
But to make this, I must know the exact part of the string displayed in the first UILabel, which is not easy because of the randomness of the string and the linebreaks.
So, is there a way to get just the text displayed in the first UILabel, without the truncated part of the string?
if ([_infoMedia.description length] > 270) {
NSRange labelLimit = [_infoMedia.description rangeOfString:#" " options:NSCaseInsensitiveSearch range:NSMakeRange(270, (_infoMedia.description.length - 270))];
_descTop.text = [_infoMedia.description substringToIndex:labelLimit.location];
_descBottom.text = [_infoMedia.description substringFromIndex:(labelLimit.location+1)];
} else {
_descTop.text = _infoMedia.description;
_descBottom.text = #"";
}
Okay that's a late answer but maybe it could help someone. The code above is approximatively the solution I used in my app.
_descTop is my first label and _descBottom is the second label. 270 is a constant equivalent to a little less than the average maximum number of characters displayed in my first label, _descTop. I calculated it by hand, trying with many different strings, maybe there's a better way to do that but this worked not bad.
If the string I want to display (_infoMedia.description) is larger than 270 characters, I isolate the first 270 characters plus the end of the next word in the string (by searching the next space), in the case where the 270 characters limit would cut the string in the middle of a word. Then I put the first part of the string in my first label, and the second part in the second label.
If not, I only put the globality of the string in the first label.
I know that's a crappy solution, but it worked and I didn't found any better way to do that.
Following code might help you in getting what you want!!
//If you want the string displayed in any given rect, use the following code..
#implementation NSString (displayedString)
//font- font of the text to be displayed
//size - Size in which we are displaying the text
-(NSString *) displayedString:(CGSize)size font:(UIFont *)font
{
NSString *written = #"";
int i = 0;
int currentWidth = 0;
NSString *nextSetOfString = #"";
while (1)
{
NSRange range;
range.location = i;
range.length = 1;
NSString *nextChar = [self substringWithRange:range];
nextSetOfString = [nextSetOfString stringByAppendingString:nextChar];
CGSize requiredSize = [nextSetOfString sizeWithFont:font constrainedToSize:CGSizeMake(NSIntegerMax, NSIntegerMax)];
currentWidth = requiredSize.width;
if(size.width >= currentWidth && size.height >= requiredSize.height)
{
written = [written stringByAppendingString:nextChar];
}
else
{
break;
}
i++;
}
return written;
}
#end