Draw background color in a UILabel rect only where my text is - iphone

I need to create a custom UILabel to display some dynamic multiline text. The text color is white on a black background. But the background should only be visible right behind the text to simulate the effect of an selected text area.
I started with subclassing UILabel and overriding drawTextInRect to do my own drawings.
- (void) drawTextInRect:(CGRect)rect
{
/* do some custom drawings here */
[super drawTextInRect:rect];
}
So far i could not figure out a way to compute the text-bounds do draw my background into.
Does anybody now how do do this kind of stuff? Thanks a lot.

NSString has some additions in UIKit to allow for calculating the size used to render the string, given various parameters. You can use these methods to calculate the size the UILabel needs to render the string, and then resize the UILabel to precisely this size. Look in the documentation for a method called -sizeWithFont:, all of the other variations are listed there. Make sure you use the right method to match how your UILabel is configured.
There is one caveat here, which is that on iOS 4, there is a bug where these methods actually return a slightly different size than is actually used for drawing (at least for the system font on iPhone 4's [e.g. Helvetica Neue], I don't know if this bug affects any other fonts). Unfortunately the only workaround I know of is to switch to Core Text for all your text rendering, so you may just prefer to live with this bug (if it even affects you) until Apple pushes out a software update. This bug does affect Apple's own applications so there is plenty of precedent for not handling it.

Ok. Now after some input from stackoverflow i am using this code snippet to get the textbounds but it only works fine with single line text.
- (void) drawTextInRect:(CGRect)rect
{
CGFloat lineHeight = [self.text sizeWithFont:self.font].height;
CGSize testSize = CGSizeMake(320, lineHeight * self.numberOfLines);
CGSize textSize = [self.text sizeWithFont:self.font constrainedToSize:testSize lineBreakMode:self.lineBreakMode];
//NSLog(#"drawTextInRect lineHeight %f, width %f x height %f", lineHeight, textSize.width, textSize.height);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextAddRect(context, CGRectMake(0, 0, textSize.width, textSize.height));
CGContextFillPath(context);
CGContextRestoreGState(context);
[super drawTextInRect:rect];
}
If i have multiline text i still need some code to compute the path-information of the text and not just the bounding rect so that i can use CGContextDrawPath to draw my background. Any thoughts on that? Thanks.

Related

iPhone : Underline Text

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

How to underline UILabel in iPhone SDK? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
underline text in UIlabel
Can any one provide a code to show me how to underline text in UILabel? In Interface Builder I underline the UILabel text but it's not working.
You can inheritate UILabel and draw a line yourself in drawTextInRec
you need to implement the drawRect: method of your UIView inherited class.
- (void)drawRect:(CGRect)rect
{
// Get the Render Context
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Measure the font size, so the line fits the text.
// Could be that "titleLabel" is something else in other classes like UILable, dont know.
// So make sure you fix it here if you are enhancing UILabel or something else..
CGSize fontSize =[self.titleLabel.text sizeWithFont:self.titleLabel.font
forWidth:self.bounds.size.width
lineBreakMode:UILineBreakModeTailTruncation];
// Get the fonts color.
const float * colors = CGColorGetComponents(self.titleLabel.textColor.CGColor);
// Sets the color to draw the line
CGContextSetRGBStrokeColor(ctx, colors[0], colors[1], colors[2], 1.0f); // Format : RGBA
// Line Width : make thinner or bigger if you want
CGContextSetLineWidth(ctx, 1.0f);
// Calculate the starting point (left) and target (right)
float fontLeft = self.titleLabel.center.x - fontSize.width/2.0;
float fontRight = self.titleLabel.center.x + fontSize.width/2.0;
// Add Move Command to point the draw cursor to the starting point
CGContextMoveToPoint(ctx, fontLeft, self.bounds.size.height - 1);
// Add Command to draw a Line
CGContextAddLineToPoint(ctx, fontRight, self.bounds.size.height - 1);
// Actually draw the line.
CGContextStrokePath(ctx);
// should be nothing, but who knows...
[super drawRect:rect];
}
Taken form iPhone Programming how to underline UILabel
You could also inherit UILabel . See the belwo SO post, which does exactly the same.
Underline text in UIlabel

How to use a CGBlendMode on a UIView that scrolls above a fixed background?

Our main UIView is a UIScrollView with a fixed background image (very common, obviously). In that scrollView, we have several UIViews that hold content and scroll up and down as the user scrolls (also common). Those UIViews each have their own background, a simple gradient from white to black.
The goal is to have the background gradient of those (inner) UIViews be partially opaque AND use a CGBlendMode other than "kCGBlendModeNormal" (specifically, "kCGBlendModeOverlay"). You should be able to see through to the "parent" scrollView’s fixed background image as the UIViews scroll up and down above it.
- (void)drawRect:(CGRect)rect {
gradientStart = [UIColor colorWithRed:1 green:1 blue:1 alpha:1.0];
gradientEnd = [UIColor colorWithRed:0 green:0 blue:0 alpha:1.0];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[2] = { 0.0f, 1.0f };
NSArray *colors = [NSArray arrayWithObjects:(id)gradientStart.CGColor, (id)gradientEnd.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations);
CGColorSpaceRelease(colorSpace);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAlpha(context, 0.50); //this works!
CGContextSetBlendMode(context, kCGBlendModeOverlay); //doesn’t seem to do anything!
CGContextClearRect(context, rect);
CGPoint startPoint, endPoint;
startPoint.x = 0.0;
startPoint.y = 0.0;
endPoint.x = 0.0;
endPoint.y = rect.size.height;
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
[super drawRect:rect];
}
Everything works as expected except the CGContextSetBlendMode, which is ignored. We can't seem to find a way to change the blendMode of a UIView relative to what is behind it, the same way you can with alpha. Please note that this is different than building up multiple layers in a SINGLE UIView; in that case, this technique does change the blendMode of the layers "on top". We want to see through to the parent scrollView's fixed background image (as we scroll the child view up and down above it), with both an alpha and an overlay blend applied.
Here's an image showing the issue: http://img2.sbck.us/blendmode.png
Thanks in advance for your help!
I believe what you want is not possible with your current setup. On iOS, it is simply not possible for the blend mode of a view to have an effect on the stuff that is drawn under the view. You would have to draw the scroll view's background and the gradients in the same view.
This is possible, at least with two image views. It might even be possible with more general views. The approach is to implement drawRect in the parent view, and do as follows:
Determine the rect for the foreground view.
Convert the rect in the foreground view to a rect in the background view.
Begin a new graphics context.
Draw the background with the proper blend mode.
Draw the foreground with the proper blend mode.
Extract the image from the graphics context.
End the graphics context.
Use the extracted image accordingly.
This allows a foreground image to blend with a background image.
Seems like you could do this by setting the 'compositingFilter' property of your view's CALayer. The comment in CALayer.h says "A filter object used to composite the layer with its (possibly filtered) background. Default value is nil, which implies source-over compositing."
Alas, CoreImage which provides the filters is not (officially) available on iOS.
I guess your other alternative would be to use OpenGL. You could still use UIView with OpenGL after a fashion by rendering your UIView's into images which could then be used a textures.

kCGBlendModeClear doesn't clear, why?

I'm writing some lines of code to get acquainted with the basics of Quartz 2d.
I am trying to draw and image an then clear it through the kCGBlendModeClear blend mode. Here's the code of my UIView subclass code, whose background color is set to orange through IB:
- (void)drawRect:(CGRect)rect {
UIImage *brush = [UIImage imageNamed:#"brush.png"] ;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx,kCGBlendModeNormal );
CGContextDrawImage(ctx, CGRectMake(100, 100, 26, 25), [brush CGImage]);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextFillEllipseInRect(ctx, CGRectMake(110, 110, 5, 5)); // HERE!
}
Reading the docs and this question I thought that line marked HERE would produce a hole in the image I had previously drawn. Instead it creates a black circle on it (should be orange).
To debug, I tried adding my custom view over an orange uiview. This time my custom view has a black background. The hole of line HERE is correct, but I wonder why the black color of the view. Even more strangely, if I do myView.backgroundColor I can set a background color (shouldn't this be overridden by my drawRect implementation?).
I am clearly missing some basics of Quartz, can anyone help?
Davide
Couple of things.
First, the Porter-Duff blend modes are only guaranteed to work in bitmap-based contexts. (i.e., contexts created by CGBitmapContextCreate)
Second, kCGBlendModeClear is defined as R = 0 — it doesn't even check src or dst pixels. So even though it's not guaranteed to work here, it appears that it is working correctly.
(see CGContext.h for better explanation than the docs give.)
For kCGBlendModeClear to work the view must not be opaque and must not have a background color set.
In addition, the following line is unnecessary, because kCGBlendModeClear does not care about the fill color:
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);

Drop Shadow on UITextField text

Is it possible to add a shadow to the text in a UITextField?
As of 3.2, you can use the CALayer shadow properties.
_textField.layer.shadowOpacity = 1.0;
_textField.layer.shadowRadius = 0.0;
_textField.layer.shadowColor = [UIColor blackColor].CGColor;
_textField.layer.shadowOffset = CGSizeMake(0.0, -1.0);
I have a slightly different problem - I want a blurred shadow on a UILabel. Luckily, the solution to this turned out to be number (2) from Tyler
Here's my code :
- (void) drawTextInRect:(CGRect)rect {
CGSize myShadowOffset = CGSizeMake(4, -4);
CGFloat myColorValues[] = {0, 0, 0, .8};
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(myContext);
CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues);
CGContextSetShadowWithColor (myContext, myShadowOffset, 5, myColor);
[super drawTextInRect:rect];
CGColorRelease(myColor);
CGColorSpaceRelease(myColorSpace);
CGContextRestoreGState(myContext);
}
This is in a class that extends from UILabel and draws the text with a shadow down and to the right 4px, the shadow is grey at 80% opacity and is sightly blurred.
I think that Tyler's solution number 2 is a little better for performance than Tyler's number 1 - you're only dealing with one UILabel in the view and, assuming that you're not redrawing every frame, it's not a hit in rendering performance over a normal UILabel.
PS This code borrowed heavily from the Quartz 2D documentation
I don't think you get built-in support for text shadows here, the way you do with UILabel.
Two ideas:
(1) [Moderately tricky to code.] Add a second UITextField behind the original, at a very small offset (maybe by (0.2,0.8)? ). You can listen to every text change key-by-key by implementing the textField:shouldChangeCharactersInRange:replacementString: method in the UITextFieldDelegate protocol. Using that, you can update the lower text simultaneously. You could also make the lower text (the shadow text) gray, and even slightly blurry using the fact that fractionally-offset text rects appear blurry. Added: Oh yea, don't forget to set the top text field's background color to [UIColor clearColor] if you go with this idea.
(2) [Even more fun to code.] Subclass UITextField and override the drawRect: method. I haven't done this before, so I'll mention up front that this depends on this being the designated drawing method, and it may turn out that you have to override another drawing function, such as drawTextInRect:, which is specific to UITextField. Now set up the drawing context to draw shadows via the CGContextSetShadow functions, and call [super drawRect:rect];. Hopefully that works -- in case the original UITextField code clears the drawing context's shadow parameters, that idea is hosed, and you'll have to write the whole drawing code yourself, which I anti-recommend because of all the extras that come with UITextFields like copy-and-paste and kanji input in Japanese.
Although the method of applying the shadow directly to the UITextView will work, it's the wrong way to do this. By adding the shadow directly with a clear background color, all subviews will get the shadow, even the cursor.
The approach that should be used is with NSAttributedString.
NSMutableAttributedString* attString = [[NSMutableAttributedString alloc] initWithString:textView.text];
NSRange range = NSMakeRange(0, [attString length]);
[attString addAttribute:NSFontAttributeName value:textView.font range:range];
[attString addAttribute:NSForegroundColorAttributeName value:textView.textColor range:range];
NSShadow* shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor whiteColor];
shadow.shadowOffset = CGSizeMake(0.0f, 1.0f);
[attString addAttribute:NSShadowAttributeName value:shadow range:range];
textView.attributedText = attString;
However textView.attributedText is for iOS6. If you must support lower versions, you could use the following approach. (Dont forget to add #import <QuartzCore/QuartzCore.h>)
CALayer *textLayer = (CALayer *)[textView.layer.sublayers objectAtIndex:0];
textLayer.shadowColor = [UIColor whiteColor].CGColor;
textLayer.shadowOffset = CGSizeMake(0.0f, 1.0f);
textLayer.shadowOpacity = 1.0f;
textLayer.shadowRadius = 0.0f;