Draw underlined / strikethrough text ( MULTILINE STRING )? - iphone

I have to draw underlined-multiline text with all types of text alignment. I have searched on forums and got some results like:
http://davidjhinson.wordpress.com/2009/11/26/underline-text-on-the-iphone/
http://forums.macrumors.com/showthread.php?t=561572
But all draw text for single line only. while i have multi-line text. The situation even become worse when the text alignment is centered. I searched and found that in iphone-sdk-3.2 there are some core-text attributes for underlining a text but no idea how to use that. Besides if I use these my problem would not be solved fully.
As I have to draw strikethrough text also.
Anybody having idea about this please help.

What about this. This works good for me what you all think, if this needs some optimisation?
CGContextSetFillColorWithColor(c, [[UIColor blackColor] CGColor]); //THE TEXT COLOR OF THE STRING TO BE DRAWN.
UIFont *fontName = [UIFont fontWithStyle:#"Arial-BoldMT" andFontSize:13];
if(FontOverLayUnderLine || FontOverLayStrikeThrough){
NSArray *stringsArray = [textString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
CGContextSetStrokeColorWithColor(c, [appDel.textDisplayStyle.fillColor CGColor]);
CGContextSetLineWidth(c, 1.0);
CGSize stringSize;
NSString *stringToDraw;
for (int i = 0 ; i < [stringsArray count]; i++) {
stringToDraw = [stringsArray objectAtIndex:i];
if(![[stringToDraw stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:#""]){
stringSize = [stringToDraw sizeWithFont:fontName forWidth:rect.size.width lineBreakMode:UILineBreakModeCharacterWrap];
float x = rect.origin.x + (rect.size.width-stringSize.width)/2 + 7;
float y = 4 + stringSize.height*i;
[stringToDraw drawAtPoint:CGPointMake(x, y) forWidth:stringSize.width withFont:fontName lineBreakMode:UILineBreakModeCharacterWrap];
if(FontOverLayUnderLine)
y += (1.05) * stringSize.height*i +1;
else
y += (stringSize.height/2)+1;
CGContextMoveToPoint(c, x, y);
CGContextAddLineToPoint(c, x+stringSize.width, y);
CGContextStrokePath(c);
}
}
}
Hope this works good for all.
Thanks,
Madhup

Use Core Text.
I searched and found that in iphone-sdk-3.2 there are some core-text attributes for underlining a text but no idea how to use that.
That's what the documentation is for.

Related

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).

striking through the text

I need to strike through the text of a multi-line label. Is there a way to do it?
Any suggestion would be greatly helpful. Thanks,
if you want do it with UILabel for iPhone you can't :(
so there are 3 ways:
(simplest) use UIWebView:
// set html header with styles, you can certainly use some other attributes
NSString * htmlWrap = #"<html><head><style>body{text-align:left; background-color:transparent; color:black; font-weight:bold; text-decoration:line-through; font-size:%dpt}`</style></head>`<body>%#</body`></html>";
NSStrring * myText = #"My sample strikethrough text";
webView.backgroundColor = [UIColor clearColor];
[webView setOpaque:NO];
NSString * htmlText = [NSString stringWithFormat:htmlWrap, 12, myText];
[webView loadHTMLString:htmlText baseURL:nil];
use unicode combining diacritic (this works with any objects labels, textfields etc.)
"long stroke overlay" (U+0336) or
"combining low line" (U+0332) before
each charecter in your string. Use
-(void)getCharacters:(unichar *)buffer range:(NSRange)aRange
to create unichar array from string (allocate double size of string length), then rearrange array and add U+0336 or U+0332 before each character, then convert unichar array back to NSString with
-(id)initWithCharacters:(const unichar *)characters length:(NSUInteger)length
but in most cases this looks bad
Draw it manualy on context.
This will work for a single line label.
#interface UILabelStrikeThrough : UILabel {
}
#end
#implementation UILabelStrikeThrough
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat black[4] = {0.0f, 0.0f, 0.0f, 1.0f};
CGContextSetStrokeColor(c, black);
CGContextSetLineWidth(c, 2);
CGContextBeginPath(c);
CGFloat halfWayUp = (self.bounds.size.height - self.bounds.origin.y) / 2.0;
CGContextMoveToPoint(c, self.bounds.origin.x, halfWayUp );
CGContextAddLineToPoint(c, self.bounds.origin.x + self.bounds.size.width, halfWayUp);
CGContextStrokePath(c);
[super drawRect:rect];
}
#end
Improved #RefuX code to handle location and width of the strikethrough.
I will add multiline support soon.
Multiple lines now supported
Gist for UILabelStrikethrough

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;
}

How i access the array of CGPoints to draw the graph?

I have the code in which i want to develop the graph.The code is,
NSArray *coordinate = [[NSArray alloc] initWithObjects: #"42,213", #"75,173", #"108,153", #"141,133", #"174,113", #"207,73", #"240,33", nil];
CGContextSetRGBFillColor(ctx, 255, 0, 0, 1.0);
CGContextSetLineWidth(ctx, 8.0);
for(int intIndex = 0; intIndex < [coordinate count]; fltX1+=33, intIndex++)
{
CGContextMoveToPoint(ctx, fltX1+37, fltY2+18);
CGPoint point = [[coordinate objectAtIndex:intIndex] CGPointValue];
CGContextAddLineToPoint(ctx, point);
CGContextStrokePath(ctx);
}
I the above code goes in to debugger at the line of CGPoint point = [[coordinate objectAtIndex:intIndex] CGPointValue].
How i drawing the line in the graph using above code????????
Now i changing the above code as fllows,
Hi,glorifiedHacker,I already guessing above sentence.
But, now i changing the my code like,
CGContextSetRGBFillColor(ctx, 255, 0, 0, 1.0);
CGContextSetLineWidth(ctx, 8.0);
NSArray *coordinate1 = [[NSArray alloc] initWithObjects:#"42",#"75",#"108",#"141",#"174",#"207",#"240",nil];
NSLog(#"The points of coordinate1: %#", coordinate1);
NSArray *coordinate2 = [[NSArray alloc] initWithObjects:#"213",#"173",#"153",#"133",#"113",#"73",#"33",nil];
for(int intIndex = 0; intIndex < [coordinate1 count], intIndex < [coordinate2 count]; fltX1+=33, intIndex++)
{
CGContextMoveToPoint(ctx, fltX1+37, fltY2+18);
NSString *arrayDataForCoordinate1 = [coordinate1 objectAtIndex:intIndex];
NSString *arrayDataForCoordinate2 = [coordinate2 objectAtIndex:intIndex];
CGContextAddLineToPoint(ctx, (float *)arrayDataForCoordinate1, (float *)arrayDataForCoordinate2);
//NSLog(#"CGPoints of drawing the bar: %#", point);
}
CGContextClosePath(ctx);
CGContextStrokePath(ctx)
But it still given me error on same line.
Your graphics context likely doesn't have a path for you to add lines to at this point. Try wrapping your for loop in the appropriate "begin path" and "close path" function calls.
CGContextBeginPath(ctx);
for(int intIndex = 0; intIndex < [coordinate count]; fltX1 += 33, intIndex++) {
CGContextMoveToPoint(ctx, fltX1+37, fltY2+18);
CGPoint point = [[coordinate objectAtIndex:intIndex] CGPointValue];
CGContextAddLineToPoint(ctx, point.x, point.y);
}
CGContextClosePath(ctx);
CGContextStrokePath(ctx);
Also, note that I moved the "stroke path" call outside of the for loop until you have closed the path.
Instead of typecasting an (NSString *) to a (float *) try [myString doubleValue]. If you have strings of the form "{x,y}" you can use CGPointFromString to convert directly to a point. If you can, avoid strings altogether and keep the original data as numeric types. If you must use an NSArray, that could be NSNumber pairs or NSValue of CGPoint.
I the above code goes in to debugger at the line of CGPoint point = [[coordinate objectAtIndex:intIndex] CGPointValue].
Because NSStrings don't respond to CGPointValue messages. You can see this for yourself by looking at the NSString documentation (or the Debugger Console, which will contain an exception message telling you this).
How i drawing the line in the graph using above code????????
Parse the strings yourself using NSScanner, or use the NSPointFromString function, or use a C array (not an NSArray) of NSPoints/CGPoints.
On an unrelated note:
for(int intIndex = 0; intIndex < [coordinate count]; fltX1+=33, intIndex++)
{
CGContextMoveToPoint(ctx, fltX1+37, fltY2+18);
Don't just drop random numeric literals into your code. You will have no idea what these numbers mean six months from now, and if you ever want to change, say, the horizontal offset of the graph, you will have to replace the number 37 everywhere in your app (except where it isn't the horizontal offset of the graph), not to mention any numbers that are 37 plus some other offset.
Instead, give these numbers names using something like this:
enum {
MyDistanceBetweenPoints = 33,
MyGraphOffsetX = 37,
MyGraphOffsetY = 18,
};
This way, if you want to change the horizontal offset of the graph, you don't need to hunt down every last relevant numeric literal; you need only change the definition of MyGraphOffsetX.
You can also use the current transformation matrix to offset the graph, thereby eliminating the additions from every CGContextMoveToPoint call and eliminating the risk that you'll forget an addition.

iPhone: Draw rotated text?

I want to draw some text in a view, rotated 90°. I'm pretty new to iPhone development, and poking around the web reveals a number of different solutions. I've tried a few and usually end up with my text getting clipped.
What's going on here? I am drawing in a fairly small space (a table view cell), but there has to be a "right" way to do this… right?
Edit: Here are a couple of examples. I'm trying to display the text "12345" along the black bar at the left.
First attempt, from RJShearman on the Apple Discussions
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSelectFont (context, "Helvetica-Bold", 16.0, kCGEncodingMacRoman);
CGContextSetTextDrawingMode (context, kCGTextFill);
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextSetTextMatrix (context, CGAffineTransformRotate(CGAffineTransformScale(CGAffineTransformIdentity, 1.f, -1.f ), M_PI/2));
CGContextShowTextAtPoint (context, 21.0, 55.0, [_cell.number cStringUsingEncoding:NSUTF8StringEncoding], [_cell.number length]);
CGContextRestoreGState(context);
(source: deeptechinc.com)
Second attempt, from zgombosi on iPhone Dev SDK. Identical results (the font was slightly smaller here, so there's less clipping).
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint point = CGPointMake(6.0, 50.0);
CGContextSaveGState(context);
CGContextTranslateCTM(context, point.x, point.y);
CGAffineTransform textTransform = CGAffineTransformMakeRotation(-1.57);
CGContextConcatCTM(context, textTransform);
CGContextTranslateCTM(context, -point.x, -point.y);
[[UIColor redColor] set];
[_cell.number drawAtPoint:point withFont:[UIFont fontWithName:#"Helvetica-Bold" size:14.0]];
CGContextRestoreGState(context);
Attempt two. There is almost identical clipping http://dev.deeptechinc.com/sidney/share/iphonerotation/attempt2.png
It turns out that the my table cell was always initialized 44px high regardless of the row height, so all of my drawing was getting clipped 44px from the top of the cell.
To draw larger cells it was necessary to set the content view's autoresizingMask with
cellContentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
or
cellContentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
…and drawRect is called with the correct size. In a way, this makes sense, because UITableViewCell's initWithStyle:reuseIdentifier: makes no mention of the size of the cell, and only the table view actually knows how big each row is going to be, based on its own size and its delegate's response to tableView:heightForRowAtIndexPath:.
I read the Quartz 2D Programming Guide until the drawing model and functions started to make sense, and the code to draw my rotated text became simple and obvious:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextRotateCTM(context, -(M_PI/2));
[_cell.number drawAtPoint:CGPointMake(-57.0, 5.5) withFont:[UIFont fontWithName:#"Helvetica-Bold" size:16.0]];
CGContextRestoreGState(context);
Thanks for the tips, it looks like I'm all set.
Use :-
label.transform = CGAffineTransformMakeRotation(- 90.0f * M_PI / 180.0f);
where label is the object of UILabel.
Here's a tip. I presume you're doing this drawing in drawRect. Why don't you draw a frame around drawRect to see how big the rect is and if that is why you get clipping.
An alternative is to put your text in a UILabel, and then rotate that 90 degrees when you make your cells in cellForRowAtIndexPath.
You know about the UITableViewDelegate method heightForRowAtIndexPath right?
Here's a simple tutorial on various graphics level methods. Presuming you know how big your text is you should be able to size your table view row size appropriately.
Also, I'd check to make sure that the bounds after any transform actually meet your expectations. (Either use a debugger or log statement to verify this).
to what #Sidnicious said, and what i collected through out stack overflow, i want to give a usage example - appended my code to completely draw a ruler to the left screen side, with numbers rotated:
RulerView : UIView
// simple testing for iPhones (check for device descriptions to get all iPhones + iPads)
- (float)getPPI
{
switch ((int)[UIScreen mainScreen].bounds.size.height) {
case 568: // iPhone 5*
case 667: // iPhone 6
return 163.0;
break;
case 736: // iPhone 6+
return 154.0;
break;
default:
return -1.0;
break;
}
}
- (void)drawRect:(CGRect)rect
{
[[UIColor blackColor] setFill];
float ppi = [self getPPI];
if (ppi == -1.0) // unable to draw, maybe an ipad.
return;
float linesDist = ppi/25.4; // ppi/mm per inch (regular size iPad would be 132.0, iPhone6+ 154.0)
float linesWidthShort = 15.0;
float linesWidthMid = 20.0;
float linesWidthLong = 25.0;
for (float i = 0, c = 0; i <= self.bounds.size.height; i = i + linesDist, c = c +1.0)
{
bool isMid = (int)c % 5 == 0;
bool isLong = (int)c % 10 == 0;
float linesWidth = isLong ? linesWidthLong : isMid ? linesWidthMid : linesWidthShort;
UIRectFillUsingBlendMode( (CGRect){0, i, linesWidth, .5} , kCGBlendModeNormal);
/* FONT: Numbers without rotation (yes, is short)
if (isLong && i > 0 && (int)c % 10 == 0)
[[NSString stringWithFormat:#"%d", (int)(c/10)] drawAtPoint:(CGPoint){linesWidthLong +2, i -5} withAttributes:#{
NSFontAttributeName: [UIFont systemFontOfSize:9],
NSBaselineOffsetAttributeName: [NSNumber numberWithFloat:1.0]
}];
*/
// FONT: Numbers with rotation (yes, requires more effort)
if (isLong && i > 0 && (int)c % 10 == 0)
{
NSString *str = [NSString stringWithFormat:#"%d", (int)(c/10)];
NSDictionary *attrs = #{
NSFontAttributeName: [UIFont systemFontOfSize:9],
NSBaselineOffsetAttributeName: [NSNumber numberWithFloat:0.0]
};
CGSize textSize = [str sizeWithAttributes:attrs];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextRotateCTM(context, +(M_PI/2));
[str drawAtPoint:(CGPoint){i - (textSize.width/2), -(linesWidthLong + textSize.height +2)} withAttributes:attrs];
CGContextRestoreGState(context);
}
}
}
After I discovered that I needed to add the following to the top of my file I liked Matt's approach. Very simple.
#define degreesToRadian(x) (M_PI * (x) / 180.0)
mahboudz's suggestion will probably be your path of least resistance. You can rotate the UILabel 90deg with this: [label setTransform:CGAffineTransformMakeRotation(DegreesToRadians(-90.0f))]; You'll just have to calculate your cell height based upon the label width. -Matt – Matt Long Nov 10 at 0:09