I am developing an Application in which I am using Core Text for layout purpose. I have used CTFrameSetter to draw the text.I have inserted a blank character '\ufffc' in my text. Now what i want is to determine the position of this blank character i.e. its x, y coordinates. I have not been able to find any function to determine the position of any specific character.
I tried using CTLineGetOffsetForStringIndex( CTLineRef line, CFIndex charIndex, CGFloat* secondaryOffset ); for finding the position, but I couldn't fully understand the working of this method.
Can anyone provide me some pointers on this issue?
you can loop all the CTLines of the current CTFrameRef, and for each ctline, get the CTRun and check if that CTRun is yours. simple code
NSArray *lines = (NSArray*)CTFrameGetLines(workFrame);
NSInteger count = [lines count];
CGPoint *origins = (CGPoint*)malloc(count * sizeof(CGPoint));
CTFrameGetLineOrigins(workFrame, CFRangeMake(0, count), origins);
for (int i = 0; i < lines.count; i++) {
CTLineRef line = (CTLineRef)[lines objectAtIndex:i];
CFRange cfRange = CTLineGetStringRange(line);//the string-range
NSArray * runArray = (NSArray *)CTLineGetGlyphRuns(line);
int runIndex = 0;
for (id runObj in runArray) {
CTRunRef run = (CTRunRef)runObj;
CFRange runRange = CTRunGetStringRange(run);
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, runRange.location + runRange.length, NULL);
//checks if the crrun's text is your space
}
}
Related
Frrom UITextview i am able to get number of lines for given text, if its more than 4 lines i want to truncate the rest, so how to get the index to substring?
It will restrict to user to enter more than 4 lines in a UITextview
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText
{
if (textView.contentSize.height/textView.font.lineHeight>4) {
return NO;
}
else
return YES;
}
NSMutableString *str = [[NSMutableString alloc] initWithString:textView.text];
static int numberOfLines = 0;
int nOC = 1;
while (nOC < [textView.text count] && numberOfLines < 4) {
if ([[str substringWithRange:NSMakeRange(nOC,1)] isEqualToString:#"\n"]; ) {
numberOfLines++;
}
nOC++;
}
NSString *finalString = [str substringWithRange:NSMakeRange(1,nOC)];
i hope this should work out for u. i calculated the number of characters till the 4th "\n" and used substringWithRange to extract the desired string. i didnt try this piece of code but this logic should work or at least assist u in your code. happy coding :)
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++;
}
How can I tell UITextView to set the cursor to a particular line (line 13, for example) when it appears?
UITextView has a method setSelectedRange: (NSRange) range.
If you know where in the string line 13 occurs, say location 237, then do: [textView setSelectedRange: NSMakeRange(237,0)];
If you need to find out where line 13 occurs, you've more work ahead. I'd start by looking at sizeWithFont, remembering to nip about 16 points off the width of your textView so that iOS gets the sums right. (That said, if you have line breaks, then just find the location of the 13th (or nth) "\n".)
Update following your further query in the comment
There are lots of ways of finding the position of the nth \n. The following snippet isn't pretty, but it will do the job. You could also use rangeOfString and iterate through the "\n". In this snippet, if the target line is greater than the number of lines, it puts the cursor at the end. The code here assumes you have a UITextView property called userEntry.
int targetLine = 3; // change this as appropriate 0=first line
NSRange range;
NSString* exampleString = #"Hello there\nHow is it going?\nAre you looking for a new line?\nA new line in what?\nThat remains to be seen";
NSArray* separateLines = [exampleString componentsSeparatedByString:#"\n"];
if (targetLine < [separateLines count])
{
int count = 0;
for (int i=0; i<targetLine; i++)
{
count = count + [[separateLines objectAtIndex:i] length] + 1; // add 1 to compensate \n separator
}
range = NSMakeRange(count, 0);
}
else
{
range = NSMakeRange([exampleString length], 0); // set to the very end if targetLine is > number of lines
}
[[self userEntry] setText: exampleString];
[[self userEntry] setSelectedRange:range];
[[self userEntry] becomeFirstResponder];
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
I am trying to create a cool score counter in my iPhone game, where I created the digits 0 to 9 in photoshop and I want to update the score every second.
What I am doing now is the following:
In my init I load all the digit sprites into an array, so that the array has 10 items.
I created a method which breaks down the current score (eg. 2000) into single digits and gets the sprites from the array and after that adds them to a parent CocosNode* object.
Every second I get the parent CocosNode by it's tag and replace it with the new parent object.
Currently I am already running into problems with this, because the score 2000 uses the 0-digit three times and I cannot re-use sprites.
- (CocosNode*) createScoreString:(int) score
{
NSLog(#"Creating score string : %d", score);
NSString* scoreString = [NSString stringWithFormat:#"%d", score];
int xAxes = 0;
CocosNode* parentNode = [[Sprite alloc] init];
for (NSInteger index = 0; index < [scoreString length]; index++)
{
NSRange range;
range.length = 1;
range.location = index;
NSString* digit = [scoreString substringWithRange:range];
Sprite* digitSpriteOriginal = [self.digitArray objectAtIndex:[digit intValue]];
Sprite* digitSprite = [digitSpriteOriginal copy];
[digitSprite setPosition:cpv(xAxes, 0)];
xAxes += [digitSprite contentSize].width - 10;
[parentNode addChild:digitSprite];
}
return parentNode;
}
Am I handling this the right way within cocos2d or is there some standard functionality for this? Also, if this is correct, how can I 'reuse' the sprites?
I believe that you want to use the LabelAtlas class, you'll only need to provide a compatible bitmap (like one the fps counter uses).