Getting exact location of cursor in a uitextview in iPhone - iphone

How can I get the exact position of the cursor in a UITextview in iPhone. I know that the location of the cursor can be get by using
'text_view.selectedRange.location'.
I want the X and Y co-ordinates of the cursor. How can I do this?

CGPoint origin = myTextView.frame.origin;
NSString* myHead = [myTextView.text substringToIndex:textView.selectedRange.location];
CGSize initialSize = [myHead sizeWithFont:myTextView.font constrainedToSize:myTextView.contentSize];
NSUInteger startOfLine = [myHead length];
while (startOfLine > 0) {
/*
* 1. Adjust startOfLine to the beginning of the first word before startOfLine
* 2. Check if drawing the substring of head up to startOfLine causes a reduction in height compared to initialSize.
* 3. If so, then you've identified the start of the line containing the cursor, otherwise keep going.
*/
}
NSString* textTailSection = [head substringFromIndex:startOfLine];
CGSize lineTotalSize = [textTailSection sizeWithFont:myTextView.font forWidth:myTextView.contentSize.width lineBreakMode:UILineBreakModeWordWrap];
CGPoint cursorPoint = origin;
cursorPoint.x += lineTotalSize.width;
cursorPoint.y += initialSize.height - lineTotalSize.height;
cursorPoint contains the point.

Related

Can not draw NSString using drawInRect to display in a popover

In my application I have an array of strings that I am trying to draw in a pop over. Previously, I have been able to draw images in the pop over, but trying to convert this to show text I have not been successful. Listed below is the function I just to get the images from the array, and draw them.
CGRect b = self.bounds;
CGFloat columnWidth = b.size.width / columnCount;
CGFloat rowHeight = b.size.height / rowCount;
for (NSUInteger rowIndex = 0; rowIndex < rowCount; rowIndex++) {
for (NSUInteger columnIndex = 0; columnIndex < columnCount; columnIndex++) {
CGRect r = CGRectMake(b.origin.x + columnIndex * columnWidth,
b.origin.y + rowIndex * rowHeight,
columnWidth, rowHeight);
UIImage *image = [self.colors objectAtIndex:rowIndex * columnCount + columnIndex];
[image drawInRect:r];
}
}
In this function (for the text, not the one above), the row and column count have sometimes changes - for the text display, columns is always 1, and the rows are always equal to the amount of items in the array (for our testing purposes, there is 1 string in the array currently) I have tried to make the image a UIlabel instead, and changing the test of the label to the item in the string - but it still does not display when using drawInRect. Does anyone have a solution to this? I basically need to find any way to draw this text in the rect, even if its not using a label and another method (like placing the text in an image, which I also was not able to figure out). Thanks for your help.

CoreText manual line breaking: How to position the text, vertical alignment?

I'm trying to create a custom label subclass that draws rich text and in which I can set different parameters. One of the most huge request I have is about line breaking. In UILabel is fixed and sometimes it doesn't fit the graphics requirements.
Thus, helping myself with a little snippet on Apple site, I've started writing my own classes, and it works (somehow), but I'm having one problems:
If the line is only one, the text is not aligned vertically, it always starts a the top left of the screen
Here is the code I've written so far:
- (void)drawRect:(CGRect)rect
{
if (_text) {
if (!_attribstring) {
[self createAttributedString];
}
if (self.lineBreakValue == 0) {
self.lineBreakValue = 9;
}
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextSetShouldSmoothFonts(ctx, YES);
CGContextSetShouldAntialias(ctx, YES);
CGRect textRect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
//Manual Line braking
BOOL shouldDrawAnotherLine = YES;
double width = textRect.size.width;
CGPoint textPosition = CGPointMake(textRect.origin.x, textRect.origin.y+textRect.size.height-self.lineBreakValue);
;
// Initialize those variables.
// Create a typesetter using the attributed string.
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((__bridge CFAttributedStringRef ) self.attribstring);
// Find a break for line from the beginning of the string to the given width.
CFIndex start = 0;
while (shouldDrawAnotherLine) {
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);
// Use the returned character count (to the break) to create the line.
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));
// Get the offset needed to center the line.
float flush = 0.5; // centered
double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);
// Move the given text drawing position by the calculated offset and draw the line.
CGContextSetTextPosition(ctx, textPosition.x + penOffset, textPosition.y);
CTLineDraw(line, ctx);
if (line!=NULL) {
CFRelease(line);
}
// Move the index beyond the line break.
if (start + count >= [self.attribstring.string length]) {
shouldDrawAnotherLine = NO;
continue;
}
start += count;
textPosition.y-=self.lineBreakValue;
}
if (typesetter!=NULL) {
CFRelease(typesetter);
}
}
}
Can someone point me to the right direction?
Regards,
Andrea
For laying out text vertically aligned, you need to know the total height of the lines, and the y position of the first line would be:
(self.bounds.size.height - (totalLineHeight)) / 2 - font.ascent;
So you need 2 loops in your code, one for calculating the total height of the lines(you can also save char count of each line for later use in another loop for drawing), another certainly for drawing the lines starting from the y position calculated using the above formula.
Note: Height of each line can be the font size, you can also add line spacing between lines, all you need to ensure is that being consistent in calculating the line height and drawing those lines in regards of the y position.

How to find the end coordinates of UILabel?

So I am dynamically adding UILabels to a view along a "timeline". What I would like to do is figure out how to put the starting coordinates of the next UILabel a couple of pixels past where the last one was. I'm having a hard time figuring out where the last one ended. I've looked at a few similar questions and I can't seem to grasp how to do it. Everything is taking place in a for loop so I can update the xcoordinate variable each loop, but I need to know how to get the label size. Anyone have any ideas? I tried this but it didn't seem to work:
UILabel *title = [[UILabel alloc]initWithFrame:CGRectMake(labelsXpoint,
topLabelYpoint,
labelWidth,
20)];
CGSize labelSize = [title.text sizeWithFont:title.font constrainedToSize:title.frame.size
lineBreakMode:title.lineBreakMode];
labelsXpoint += labelSize.width;
This is how you would get the top right & bottom right coordinates of the title label:
CGPoint topRight = CGPointMake(title.frame.origin.x + title.frame.size.width, title.frame.origin.y);
CGPoint bottomRight = CGPointMake(title.frame.origin.x + title.frame.size.width, title.frame.origin.y + title.frame.size.height);
I would set the frame after you alter labelsXpoint. Something like:
UILabel *title = [[UILabel alloc]init];
CGSize labelSize = [title.text sizeWithFont:title.font constrainedToSize:title.frame.size lineBreakMode:title.lineBreakMode];
labelsXpoint += labelSize.width;
title.frame = CGRectMake(labelsXpoint, topLabelYpoint, labelWidth, 20);
You can use the functions: CGRectGetMaxX and CGRectGetMaxY:
CGPoint topRight = CGPointMake(CGRectGetMaxX(title.frame), title.frame.origin.y);
CGPoint bottomRight = CGPointMake(CGRectGetMaxX(title.frame), CGRectGetMaxY(title.frame));

returning the correct height of custom drawing text method

I have a few functions that I'm using to draw different type of text. For example:
- (CGFloat)drawInformation:(CGContextRef)c withLeftCol:(NSArray *)leftCol rightcol:(NSArray *)rightCol atPoint:(CGPoint)point withLineSpacing:(CGFloat)lineSpacing {
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGFloat fontSize = 16.0;
UIFont *pFont = [UIFont fontWithName:#"Arial-BoldMT" size:fontSize];
CGFloat yOffset = 0;
for (NSString *leftColStr in leftCol) {
[leftColStr drawAtPoint:CGPointMake(point.x, point.y + yOffset) withFont:pFont];
yOffset += lineSpacing;
}
yOffset = 0;
for (NSString *rightColStr in rightCol) {
[rightColStr drawAtPoint:CGPointMake(rtStart, point.y + yOffset) withFont:pFont];
yOffset += lineSpacing;
}
return [leftCol count] > [rightCol count] ? [leftCol count] * (fontSize + lineSpacing) : [rightCol count] * (fontSize + lineSpacing);
}
I didn't include the whole method, but this is the gist of it. I basically take in an array of text, and draw them in two columns. I want to return the height of the area so I know how large it is and can draw the next piece of text below it.
One thing I found is even though I set my fontSize = 16, if I do a [#"text" sizeWithFont:pFont], I actually get 18 or something. As you can see, I add in some lineSpacing as well. So what I'm wondering is if I'm returning the correct amount in this case, and also, if I should be using the fontSize, or the [#"text" sizeWithFont:pFont].height in my return statement. In its current state, when I need to draw my next block of text at the new point, it is pretty off, like 100 points, and I'm not sure why there is such a discrepancy. Thanks!
Create separate variables for the y offset in your first loop and the y offset in your second loop. Then return MAX(yOffsetLeftCol, yOffsetRightCol).

Pixel-Position of Cursor in UITextView

Is there a way of getting the position (CGPoint) of the cursor (blinking bar) in an UITextView (preferable relative to its content). I don’t mean the location as an NSRange. I need something around:
- (CGPoint)cursorPosition;
It should be a non-private API way.
Requires iOS 5
CGPoint cursorPosition = [textview caretRectForPosition:textview.selectedTextRange.start].origin;
Remember to check that selectedTextRange is not nil before calling this method. You should also use selectedTextRange.empty to check that it is the cursor position and not the beginning of a text range. So:
if (textview.selectedTextRange.empty) {
// get cursor position and do stuff ...
}
SWIFT 4 version:
if let cursorPosition = textView.selectedTextRange?.start {
// cursorPosition is a UITextPosition object describing position in the text (text-wise description)
let caretPositionRectangle: CGRect = textView.caretRect(for: cursorPosition)
// now use either the whole rectangle, or its origin (caretPositionRectangle.origin)
}
textView.selectedTextRange?.start returns a text position of the cursor, and we then simply use textView.caretRect(for:) to get its pixel position in textView.
It's painful, but you can use the UIStringDrawing additions to NSString to do it. Here's the general algorithm I used:
CGPoint origin = textView.frame.origin;
NSString* head = [textView.text substringToIndex:textView.selectedRange.location];
CGSize initialSize = [head sizeWithFont:textView.font constrainedToSize:textView.contentSize];
NSUInteger startOfLine = [head length];
while (startOfLine > 0) {
/*
* 1. Adjust startOfLine to the beginning of the first word before startOfLine
* 2. Check if drawing the substring of head up to startOfLine causes a reduction in height compared to initialSize.
* 3. If so, then you've identified the start of the line containing the cursor, otherwise keep going.
*/
}
NSString* tail = [head substringFromIndex:startOfLine];
CGSize lineSize = [tail sizeWithFont:textView.font forWidth:textView.contentSize.width lineBreakMode:UILineBreakModeWordWrap];
CGPoint cursor = origin;
cursor.x += lineSize.width;
cursor.y += initialSize.height - lineSize.height;
return cursor;
}
I used [NSCharacterSet whitespaceAndNewlineCharacterSet] to find word boundaries.
This can also be done (presumably more efficiently) using CTFrameSetter in CoreText, but that is not available in iPhone OS 3.1.3, so if you're targeting the iPhone you will need to stick to UIStringDrawing.
Yes — as in there's a method to get the cursor position. Just use
CGRect caretRect = [textView rectContainingCaretSelection];
return caretRect.origin;
No — as in this method is private. There's no public API for this.
I try to mark a selected text, i.e. I receive a NSRange and want to draw a yellow rectangle behind that text. Is there another way?
I can advise you some trick:
NSRange selectedRange = myTextView.selectedRange;
[myTextView select:self];
UIMenuController* sharedMenu = [UIMenuController sharedMenuController];
CGRect menuFrame = [sharedMenu menuFrame];
[sharedMenu setMenuVisible:NO];
myTextView.selectedRange = selectedRange
Using this code, you can know get the position of the cut/copy/past menu and there place your yellow rectangle.
I did not find a way to get the menu position witout forcing it to appear by a simulated select operation.
Regards
Assayag
Take a screenshot of the UITextView, then search the pixel data for colors that match the color of the cursor.
-(CGPoint)positionOfCursorForTextView:(UITextView)textView {
//get CGImage from textView
UIGraphicsBeginImageContext(textView.bounds.size);
[textView.layer renderInContext:UIGraphicsGetCurrentContext()];
CGImageRef textImageRef = UIGraphicsGetImageFromCurrentImageContext().CGImage;
UIGraphicsEndImageContext();
//get raw pixel data
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
uint8_t * textBuffer = (uint8_t*)malloc(Width * Height * 4);
NSUInteger bytesPerRow = 4 * Width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(textBuffer, Width, Height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, Width, Height), textImageRef);
CGContextRelease(context);
//search
for(int y = 0; y < Height; y++)
{
for(int x = 0; x < Width * 4; x += 4)
{
int red = textBuffer[y * 4 * (NSInteger)Width + x];
int green = textBuffer[y * 4 * (NSInteger)Width + x + 1];
int blue = textBuffer[y * 4 * (NSInteger)Width + x + 2];
int alpha = textBuffer[y * 4 * (NSInteger)Width + x + 3];
if(COLOR IS CLOSE TO COLOR OF CURSOR)
{
free(textBuffer);
CGImageRelease(textImageRef);
return CGPointMake(x/4, y);
}
}
}
free(textBuffer);
CGImageRelease(textImageRef);
return CGPointZero;
}