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
Related
I have a UITextField with the text right-aligned.
I wanted to change the color of the placeholder text, so I use - (void)drawPlaceholderInRect:(CGRect)rect method. It works great BUT the placeholder text is left-aligned now (the text remains right-aligned). I guess I can add some code to override it but I didn't find which one. Thanks in advance !
- (void)drawPlaceholderInRect:(CGRect)rect
{
[[UIColor redColor] setFill];
UIFont *font = [UIFont fontWithName:#"HelveticaNeue-Medium" size:18];
[[self placeholder] drawInRect:rect withFont:font];
}
Here is the code snippet based on Michael solution. You should create subclass of text field and add the below method. Below method basically changes x-position and width of place holder bounds.
- (CGRect)placeholderRectForBounds:(CGRect)bounds{
CGRect newbounds = bounds;
CGSize size = [[self placeholder] sizeWithAttributes:
#{NSFontAttributeName: self.font}];
int width = bounds.size.width - size.width;
newbounds.origin.x = width ;
newbounds.size.width = size.width;
return newbounds;
}
You've discovered that "drawInRect" is automagically drawing from the left edge going right.
What you need to do is adjust the "rect" passed to "drawInRect" to have left edge that allows the right edge of the drawn text to touch the right edge of your UITextField rect.
To do this, I'd recommend using this method: NSString's [self placeholder] sizeWithFont: constrainedToSize:] (assuming [self placeholder] is a NSString) which will give you the true width of the string. Then subtract the width from the right edge of the text field box and you have the left edge where you need to start your drawing from.
I enhanced #Saikiran's snippet a little, this works for me:
- (CGRect)placeholderRectForBounds:(CGRect)bounds
{
return self.editing ? ({CGRect bounds_ = [super placeholderRectForBounds:bounds];
bounds_.origin.x = bounds_.size.width
- ceilf(self.attributedPlaceholder.size.width)
+ self.inset.x;
bounds_.origin.y = .5f * (.5f * bounds_.size.height
- ceilf(self.attributedPlaceholder.size.height));
bounds_.size.width = ceilf(self.attributedPlaceholder.size.width);
bounds_.size.height = ceilf(self.attributedPlaceholder.size.height);
bounds_;
}) : [super placeholderRectForBounds:bounds];
}
Is there appeared easy way to underline text in iOS 4 ?? In IB maybe ? Thanks in advance...
As far as I know you cannot underline text.
You can "fake" it though by (for example) placing an UImageView or a regular View just underneath with the same color as your text. You can use strike-through etc, but cannot underline.
Edit:
You could use this approach to underline your UILabel though. You could namely use a custom UILabel. So you could create some class like CUILabel that inherits UILabel and replace its drawRect method in the #implementation section with the following:
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 0.0f/255.0f, 0.0f/255.0f, 255.0f/255.0f, 1.0f); // Your underline color
CGContextSetLineWidth(ctx, 1.0f);
UIFont *font = [UIFont systemFontOfSize:16.0f];
CGSize constraintSize = CGSizeMake(MAXFLOAT, MAXFLOAT);
CGSize labelSize;
labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
CGContextMoveToPoint(ctx, 0, self.bounds.size.height - 1);
CGContextAddLineToPoint(ctx, labelSize.width + 10, self.bounds.size.height - 1);
CGContextStrokePath(ctx);
[super drawRect:rect];
}
Just subclass UILabel and in drawRect after you draw your text just draw an simple line under the text. Take a look at StrikeUILabel it have some bugs in it but you can start from that class.
NSMutableAttributedString/NSAttributedString allows you to create text with various rich attributes like font,color,type styles (including underline). For generic details follow this link..
Introduction to Attributed String Programming Guide
Specific to underline of text, this link may help..
Changing an Attributed String
Sometimes i prefer to use UILabel as Underline with background of Underline color, no text, height 1 or 2 pixel and width as equal to Text to be underline...
I am using the method [string sizeWithFont:constrainedToSize:lineBreakMode:] to estimate the height of a textView that I am resizing. However, it seems to consistently return the incorrect size. To debug, I wrote the following code:
self.textView.font = [UIFont systemFontOfSize:14.0]; // It was this before, anyways
NSLog(#"Real width: %lf %lf", self.textView.contentSize.width, self.textView.frame.size.width);
NSLog(#"Real height: %lf", self.textView.contentSize.height);
NSLog(#"Estimated width: %lf", kOTMessageCellTextWidth);
NSLog(#"Estimated height: %lf", ([message.message sizeWithFont:[UIFont systemFontOfSize:14.0]
constrainedToSize:CGSizeMake(kOTMessageCellTextWidth, CGFLOAT_MAX)
lineBreakMode:UILineBreakModeWordWrap].height));
However, the above code reveals that I am getting inconsistent results:
Real width: 223.000000 223.000000
Real height: 52.000000
Estimated width: 223.000000
Estimated height: 36.000000
Real width: 223.000000 223.000000
Real height: 142.000000
Estimated width: 223.000000
Estimated height: 126.000000
Real width: 223.000000 223.000000
Real height: 142.000000
Estimated width: 223.000000
Estimated height: 126.000000
I noticed in this similar question that (apparently) textView has some padding that constrains its actual width. The recommendation there was the decrease the width passed to sizeWithFont: by some number. The recommended number was 11.
My question: is there any way to actually retrieve this value programmatically, or is there some documentation that I missed specifying this number? It seems like this number should be available and shouldn't have to be guess-and-checked, but I can't find a reliable way to identify it.
Thanks in advance.
OK, after a bit of (re)search, I've come to this.
UITextView has 8px of padding on each side. But also line-height is not the same with UILabel (and sizeWithFont methods' result)
in my case the font was Arial and the size was 16pt and line heights of textview is 4.5points more than uilabel's line heights. So;
I get the result (cSize) from sizeWithFont method, with giving width reduced by 16 pixels
I calculated the lineCount by cSize.height / font.lineHeight
and used cSize.height + (lineCount * 4.5) + 16.0 as the final height for textview
code (not the exact code):
CGSize constraintSize = CGSizeMake(250.0 - 16.0, MAXFLOAT);
CGSize labelSize = [message.text sizeWithFont:[UIFont fontWithName:#"Arial" size:16.0] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
CGFloat lineCount = labelSize.height / [UIFont fontWithName:#"Arial" size:16.0].lineHeight;
CGFloat additional = lineCount * 4.5; // for 16.0 pt Arial for now
return labelSize.height + additional + 16.0; // TextView fix
I'm still having a little problems and do not like the way i solved it. But seems ok for this problem. And It would be great if someone comes with a reasonable solution for this.
The fix is simple, since UITextView has 8px of padding on each side, so when you are calculating text's true size, you should minus this 16px padding (8px on double sides) first, then the result is right. Please see following code snippet:
// self.descView is an UITextView
// UITEXTVIEW_TEXT_PADDING is 8.0
CGSize constrainedSize = CGSizeMake(self.descView.contentSize.width-UITEXTVIEW_TEXT_PADDING*2, MAXFLOAT);
CGSize trueSize = [self.descView.text sizeWithFont:self.descView.font
constrainedToSize:constrainedSize
lineBreakMode:UILineBreakModeWordWrap];
CGSize contentSize = self.descView.contentSize;
contentSize.height = trueSize.height;
self.descView.contentSize = contentSize;
frame = self.descView.frame;
frame.size.height = contentSize.height ;
self.descView.frame = frame;
The result should be right.
NSString sizeWithFont is computed using Core Text. UITextView uses a Webview (internally) to render its contents.
This discrepancy leads to all sorts of headaches. Such as choosing to break lines at different locations — you'll notice comma delimited lists, and certain mathematical expression strings will break on different symbols between Core Text and UITextView. This often leads to out-by-one-line errors when measuring spaces for your strings to fit.
And if you want international character support — well, you can forget about Core Text line heights matching UITextView (no matter how many combinations of paragraph style and line height you try!).
I have found that the only way to reliably estimate the size for a UITextView given an NSString is to... actually construct the UITextView and read its fitted size (minus the 8px padding).
Here's a UIKitLineMetrics class that does exactly that.
It keeps two "dummy" text views. You update the instance with your desired font and layout width, and you can read the single and multi-line sizes out of it.
UIKitLineMetrics.h
#import <UIKit/UIKit.h>
#interface UIKitLineMetrics : NSObject
#property (nonatomic, strong) NSMutableAttributedString *attributedString;
- (CGSize) UIKitLineSizeForText:(NSString*)text;
- (CGSize) UIKitSingleLineSizeForText:(NSString*)text;
- (void) updateWithFont:(UIFont*)font andWidth:(CGFloat)width;
#end
UIKitLineMetrics.m
#import "UIKitLineMetrics.h"
#interface UIKitLineMetrics ()
#property (nonatomic, strong) UITextView *measuringTextView;
#property (nonatomic, strong) UITextView *measuringSingleTextView;
#end
#implementation UIKitLineMetrics
#synthesize measuringTextView;
#synthesize measuringSingleTextView;
#synthesize attributedString;
- (id) init
{
self = [super init];
if( self )
{
attributedString = [[NSMutableAttributedString alloc] init];
measuringTextView = [[UITextView alloc] initWithFrame:CGRectZero];
measuringSingleTextView = [[UITextView alloc] initWithFrame:CGRectZero];
}
return self;
}
- (CGSize) UIKitLineSizeForText:(NSString*)text
{
measuringTextView.text = [text stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
CGSize sz = [measuringTextView sizeThatFits:CGSizeMake(measuringTextView.frame.size.width, CGFLOAT_MAX)];
return CGSizeMake(sz.width - 16, sz.height - 16);
}
- (CGSize) UIKitSingleLineSizeForText:(NSString*)text
{
measuringSingleTextView.text = [text stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
CGSize sz = [measuringSingleTextView sizeThatFits:CGSizeMake(measuringSingleTextView.frame.size.width, CGFLOAT_MAX)];
return CGSizeMake(sz.width - 16, sz.height - 16);
}
- (void) updateWithFont:(UIFont*)font andWidth:(CGFloat)width
{
measuringTextView.font = font;
measuringSingleTextView.font = font;
measuringTextView.frame = CGRectMake(0, 0, width, 500);
measuringSingleTextView.frame = CGRectMake(0, 0, 50000, 500);
}
#end
You could try checking the contentInset value (a property of UIScrollView).
I've also struggled with the problem. I've usually gotten by by passing the sizeWithFont method a size that's smaller than the actual size of the textView. Even when adding the textview's content insets to the size, it doesn't work. The size returned is always smaller.
charlie
I use following function without much problem:
+(float) calculateHeightOfTextFromWidth:(NSString*) text: (UIFont*)withFont: (float)width :(UILineBreakMode)lineBreakMode
{
[text retain];
[withFont retain];
CGSize suggestedSize = [text sizeWithFont:withFont constrainedToSize:CGSizeMake(width, FLT_MAX) lineBreakMode:lineBreakMode];
[text release];
[withFont release];
return suggestedSize.height;
}
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.
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