Could someone please explain to me how to draw a
string using UIStringDrawing instead of using a label? here is my code but for some reason it compiles and the screen is blank...
//
// MainViewController.m
// DotH
//
#define WINDOW_FRAME [[UIScreen mainScreen] bounds]
#define SCREEN_FRAME [UIScreen mainScreen].applicationFrame
#define GRAY [UIColor grayColor]
#define BLACK [UIColor blackColor]
#implementation MainViewController
- (void)loadView {
UIView *view = [[UIView alloc] initWithFrame:SCREEN_FRAME];
[view setBackgroundColor:GRAY];
[BLACK setFill];
NSString *string = #"Hey Dude!";
[string drawAtPoint:CGPointMake(50, 50) withFont:[UIFont systemFontOfSize:14]];
self.view = view;
[view release];
}
#end
The line
[string drawAtPoint:CGPointMake(50, 50) withFont:[UIFont systemFontOfSize:14]];
needs to be in the view's drawRect: method.
This is because just before drawRect: is called, the rectangle passed as an argument to drawRect: is erased. So if you try to do custom drawing anywhere other than a view's drawRect: method, the stuff you draw will get erased whenever drawRect: is called. (Not to mention that calling drawAtPoint: is meaningless if not done by code within a UIView.)
You will need to make a custom subclass of UIView, and that subclass will need a custom drawRect: method. If you still want the view controller to be the entity responsible for deciding what string should be drawn and how, you should give your UIView subclass a method like
- (void)addString:(NSString *)string atPoint:(CGPoint)point withFont:(UIFont *)font;
That method can store that information, in, e.g., three NSMutableArrays (one of strings, one of points, and one of fonts), and increment a counter of how many strings have been added. Then, your view's drawRect: method can draw the strings described in those arrays. To add a string, your view controller just calls addString:atPoint:withFont: on your view.
Related
The UITableView I am using has a custom UItableViewCell. This custom cell has a subview (an UIView subclass) to it. I use the drawRect of the custom UIView subclass to place all the text to be displayed by the cell.
And in the drawRect (of UIView subclass) I do the following
/*
// This piece of code is called when setNeedsDisplay is called
*/
- (void)drawRect:(CGRect)rect
{
self.layer.cornerRadius = 10.0f;
self.layer.backgroundColor = [[UIColor orangeColor] CGColor];
self.layer.borderColor = [[UIColor lightGrayColor] CGColor];
self.layer.borderWidth = 3.0f;
}
However my custom cell is a black square like this
But I do see the intended behavior if I select the row. Like shown below
Whats going on ?
Your drawRect: method does not draw anything; the code that you put in there belongs in your initWithFrame: implementation.
You should manipulate the layer configuration in the initializer; in your drawRect: you should call functions of your CGContextRef based on the state of the view. For example, to draw some text you would use CGContextShowTextAtPoint, to draw some lines you would use CGContextAddLineToPoint, and so on.
See this question for information on the relationship between drawRect: and the CALayer of your UIView.
Try to set self.layer.masksToBounds = YES and (maybe) self.opaque = NO during your UIView's (the one where drawRect is overridden) initialization. (see this question)
Try to disable the selection highlight of the cell by using
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
You are doing one mistake:
Please go to the view in side the UItableViewCell and check the background color it may be black or something others, Reset it to clear color then check your result,
I want to have my buttons in my iOS app to have a red gradient. At first I was using images to do this, but then realised I can do it with QuartzCore framework. I have the following implementation file:
#import "RedButton.h"
#implementation RedButton
#synthesize gradientLayer = _gradientLAyer;
- (void)awakeFromNib;
{
// Initialize the gradient layer
self.gradientLayer = [[CAGradientLayer alloc] init];
// Set its bounds to be the same of its parent
[self.gradientLayer setBounds:[self bounds]];
// Center the layer inside the parent layer
[self.gradientLayer setPosition:
CGPointMake([self bounds].size.width/2,
[self bounds].size.height/2)];
// Insert the layer at position zero to make sure the
// text of the button is not obscured
[[self layer] insertSublayer:self.gradientLayer atIndex:0];
// Set the layer's corner radius
[[self layer] setCornerRadius:5.0f];
// Turn on masking
[[self layer] setMasksToBounds:YES];
// Display a border around the button
// with a 1.0 pixel width
[[self layer] setBorderColor:[UIColor colorWithRed:(158.0f/255.0f) green:0.0f blue:0.0f alpha:1.0f].CGColor];
[[self layer] setBorderWidth:1.0f];
[self.gradientLayer setColors:[NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:(214.0f/255.0f) green:0.0f blue:0.0f alpha:1.0f] CGColor],
(id)[[UIColor colorWithRed:(141.0f/255.0f) green:0.0f blue:0.0f alpha:1.0f] CGColor], nil]];
[[self layer] setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect;
{
[super drawRect:rect];
}
- (void)dealloc {
// Release our gradient layer
self.gradientLayer = nil;
[super dealloc];
}
#end
First question - am I right to use awakeFromNib here? Or should I be using initWithFrame?
Second question - originally I was using images and using interface builder to set the default and highlighted states of the button. Now that I'm not using images, how can I set the appearance of the button to change when it's highlighted? I just want to reverse the gradient.
Third question - I've seen it written in some places that you shouldn't subclass UIButton. If not, how would I change all my buttons to have this gradient without duplicating a lot of code?
Thanks in advance.
edit1: Misread the part about images.
You should be able to do something like this to set the button states natively as image using your gradients
// your code for setting up the gradient layer comes first
UIGraphicsBeginImageContext(CGSizeMake(1, [self bounds].size.height));
[gradientLayer renderInContext: UIGraphicsGetCurrentContext()];
UIImage *bgImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setBackGroundImage:bgImage forState:UIControlStateWhatever] // replace with correct states
============================
I recommend putting your initialization code in a function other than awakeFromNib (for the case where the button is actually NOT being used in a nib but possibly being created in code). You should create a custom initialization function and call it in both initWithCoder and initWithFrame This answer shows a pretty good pattern for doing so.
You can set the background for different states in your initialization by calling
[self setBackGroundImage: forState];
Where in this case your state would be UIControlStateHighlighted.
That aside, one argument against subclassing in this situation is that you're not actually defining any custom behavior, you're just trying to reuse some styling code. A subclass isn't necessary here and you can do something as simple as creating a formatting function somewhere (in your viewcontroller maybe, or some function in another class) that takes a UIButton as a scenario and performs all of that initialization code on it. This way, you will not have your buttons locked up in a subclass (which is useful if you actually end up using another UIButton subclass.. I for example like to use one that defines custom touch behavior allowing the button to be a non-rectangular shape (and have its touch area limited as such).
Another argument I've seen is that UIButton contains some factory functions that may return a button of a different type than your subclass, but you may never run into this issue if you don't use those functions.
I have a table cell being displayed that shows a users image, name and some text. The user's image is 50x50, but I want a border around it so I set the view to center the image and set the frame to 52x52 and then set the background color of that view to my border color. That shows a 1 pixel border around the image.
I also want to show a 30 pixel wide border on the right of the cell when the cell is selected. I've tried to do that by creating a UIView the size of the cell's frame, then adding a subview to that view with a UIView the width and background color I would like. I then set that view to the selectedBackgroundView of the cell.
The problem here is that the cell's selectedBackgroundView gets applied to the background of all views inside the cell. So when I select a cell, the images "border" gets set to the cell's selected background color, the other 30px "border" I'm adding gets changed to that background color also.
Code inside my cellForRowAtIndexPath:
cell = (UserCellView *) currentObject;
UIView *c = [[UIView alloc ] initWithFrame:CGRectMake(0, 0, 30, cell.frame.size.height)];
c.backgroundColor = [[UIColor alloc] initWithRed:64/255.0 green:64/255.0 blue:64/255.0 alpha:1.0];
UIView *v = [[UIView alloc ] initWithFrame:cell.frame];
v.backgroundColor = [[UIColor alloc] initWithRed:35/255.0 green:35/255.0 blue:35/255.0 alpha:1.0];
[v addSubview:c];
cell.selectedBackgroundView = v;
[c release];
[v release];
I'll assume that you haven't actually tested what's going on to form your analysis that it "gets applied to the background of all views inside the cell".
I did something like this:
#interface TestView : UIView {
}
#end
#implementation TestView
-(void)setBackgroundColor:(UIColor*)c {
// Breakpoint here.
NSLog("setBackgroundColor: %#",c);
[super setBackgroundColor:c];
}
#end
...
UIView * v = [[[UIView alloc] initWithFrame:(CGRect){{0,0},{20,20}}] autorelease];
v.backgroundColor = [UIColor magentaColor];
UIView * v2 = [[[TestView alloc] initWithFrame:(CGRect){{5,5},{10,10}}] autorelease];
v2.backgroundColor = [UIColor yellowColor];
[v addSubview:v2];
cell.selectedBackgroundView = v;
The end result is that -setBackgroundColor: is called from -[UITableViewCell _setOpaque:forSubview:] when the view is selected, with something like UIDeviceWhiteColorSpace 0 0 (i.e. [UIColor clearColor]).
Or, in other words, the background colour of some of the subviews are set to [UIColor clearColor] while the cell is selected, allowing selectedBackgroundView to show through. I think this happens because a common optimization is to give textLabel/detailTextLabel the table's background colour (e.g. white) so it draws faster, but this means the background colour has to be reset when the cell is selected.
The easiest fix is to use an image instead: a 1-by-1-pixel image of the correct colour in a UIImageView will work, if a bit messy. (I had this problem when drawing custom separator lines with 1-pixel-high UIViews, so I just included the separator into the background image.)
An alternative fix is to use a CALayer instead: Add a 52x52 sublayer to the UIImageView's layer, and set the sublayer's background colour. I'm pretty sure UITableViewCell simply walks the view hierarchy, so it should ignore custom layers. (The big disadvantage with layers is that they don't auto-size, which made them unsuitable for my purposes, and means the 30px right border won't auto-size.)
A workaround is to subclass the relevant views and ignore -setBackgroundColor: if it's equal to [UIColor clearColor].
A simple but obnoxious-to-maintain solution is to override setSelected:animated: and setHighlighted:animated: with implementations re-setting the various backgrounds you want. Something along the lines of:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
self.childView.backgroundColor = [UIColor blueColor]; // whichever you want
}
First add this to your file
#import <QuartzCore/QuartzCore.h>
Then turn your view into an image with...
UIView *rowView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 1.0, 60.0)];
rowView.backgroundColor = [UIColor colorWithRed:35/255.0 green:35/255.0 blue:35/255.0 alpha:1.0];
UIGraphicsBeginImageContext(rowView.bounds.size);
[rowView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *yourImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Then instead of adding a UIView to your cell, just add a UIImageView with "yourImage".
A simple solution if the affected view can be a custom subclass is to override -setBackgroundColor:
- (void)setBackgroundColor:(UIColor *)color
{
// Ignore requests and do nothing
}
Thus UITableViewCell's attempt to set the colour will go ignored. Code in the custom view which really does want to set the background colour needs to call super:
- (void)setColor:(UIColor *)color
{
[super setBackgroundColor:color];
}
(or could probably message the underlying CALayer directly)
you will need to customize the contentView of the cells and handle the delegate tableView:cellForRowAtIndexPath
See Posting Here
I have my very own minimal view class with this:
- (void) awakeFromNib
{
NSLog(#"awakeFromNib!");
[self.layer setDelegate:self];
[self.layer setFrame:CGRectMake(30, 30, 250, 250)];
self.layer.masksToBounds = YES;
self.layer.cornerRadius = 5.0;
self.layer.backgroundColor = [[UIColor redColor] CGColor];
[self setNeedsDisplay];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
NSLog(#"drawing!");
}
drawLayer:inContext never get called, although I can see the layer as red, rounded corner rectangle. What am I missing?
EDIT: from Apple docs
You can draw content for your layer,
or better encapsulate setting the
layer’s content image by creating a
delegate class that implements one of
the following methods: displayLayer:
or drawLayer:inContext:.
Implementing a delegate method to draw
the content does not automatically
cause the layer to draw using that
implementation. Instead, you must
explicitly tell a layer instance to
re-cache the content, either by
sending it a setNeedsDisplay or
setNeedsDisplayInRect: message, or by
setting its needsDisplayOnBoundsChange
property to YES.
Also
drawLayer:inContext:
If defined, called by the default implementation
of drawInContext:.
You should never change the delegate of layer of a UIView. From documentation of UIView layer property:
Warning: Since the view is the layer’s
delegate, you should never set the
view as a delegate of another CALayer
object. Additionally, you should never
change the delegate of this layer.
If you want to do custom drawing in a view simply override the drawRect: method.
If you do want to use layers you need to create your own:
UIView *myView = ...
CALayer *myLayer = [CALayer layer];
myLayer.delegate = self;
[myView.layer addSublayer:myLayer];
In both cases you need to call setNeedsDisplay on the view in the first case and on your custom layer in the second. You never call drawRect: or drawLayer:inContext: directly, they are called automatically when you call setNeedsDisplay.
Use
[self.layer setNeedsDisplay];
instead of
[self setNeedsDisplay];
It is the view.layer, not the view
Must I also do all this crazy coordinate system conversion stuff here, or is an UILabel different from an UIImageView drawing in -drawRect: ?
There's a method called - (void)drawTextInRect:(CGRect)rect for that.
BUT the documentation says: "You should not call this method directly. This method should only be overridden by subclasses that want to modify the default drawing behavior for the label’s text."
So? How to draw it then in -drawRect:?
UILabel is different in that you don't need to manually draw text to alter the way it is presented. Subclassing UILabel and overriding -drawTextInRect: is the quickest way to alter the way a UILabel is rendered. For example,
- (void)drawTextInRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShadowWithColor( context, shadowOffset, shadowRadius, [shadowColor CGColor] );
[super drawTextInRect:rect];
}
will add a shadow with a specific offset, radius, and color (as a UIColor instance) to any text that you draw in that UILabel. For an example of this in action, see a project I put together for a recent class.
However, if what you are looking to do is just draw text within another view, Vladimir's answer is the way to go.
If you perform custom drawing in your view you must not draw UILabel or another UI element but rather draw text itself. NSString has several methods for drawing in current context (look at NSString's UIKit extension docs for more methods):
- (CGSize)drawInRect:(CGRect)rect withFont:(UIFont *)font
- (CGSize)drawAtPoint:(CGPoint)point withFont:(UIFont *)font
Actually you can do it.
Try this:
UILabel *lblRef = [[UILabel alloc] initWithFrame:CGRectMake(x, y, width, height)];
lblRef.text = [refs objectAtIndex:barCount];
lblRef.adjustsFontSizeToFitWidth = TRUE;
lblRef.adjustsLetterSpacingToFitWidth = TRUE;
lblRef.textColor = self.color;
[lblRef setTextAlignment:NSTextAlignmentCenter];
lblRef.backgroundColor = [UIColor clearColor];
[self addSubview:lblRef];