Do I really need drawRect() in custom UIView? - ios

I am new to iOS and trying to understand the use for drawRect() in custom UIViews, so I have simple custom view which I initialize from code.I want to update its colors for instance and I see two approaches as shown below. Which one should I use and why?
//VController
CustomView *cv = [[CustomView alloc] initWithFrame:...]
...
[cv updateColors];
//CustomView
-(id) initWithFrame {}
-(id) initWithCoder {}
-(void) updateColors(UIColor *color){ ----(1)
...Draw here with new color ...
view1.backgroundColor = color;
view2.backgroundColor = color;
}
- (void) drawRect{
... draw here with new color ... ---------(2)
view1.backgroundColor = color;
view2.backgroundColor = color;
}

If all you want to do is change the background color of this view or some of its subviews, you absolutely should not misuse drawRect: for this. drawRect: is for when you want to draw the view (i.e. its content) when the system believes its needs refreshing; it is called at many and unpredictable times, and you don't need that - you just need to change the background color, a feature of the view, on demand. Similarly drawRect: is not the place to perform management of subviews.
But if you are going to draw the view's content (e.g. the view displays a circle and you need to draw that circle to portray the view) then you must use drawRect: for that; it is the only place where the view gets a chance to draw itself.

Related

XCode iOS custom view color

I've a UIViewController and in the main root view I've created a custom class and in drawRect I've created a layer with gradient color and added to it:
[self.layer insertSublayer:bgLayer atIndex:0];
I want to use this view to have the same background color to all my view.
Now, i've (in storyboard) added 5 labels and grouped together in a view (Editor -> Embedded In -> View).
So now I've created another subclass of UIView and in draw rect I have:
[self setBackgroundColor:[UIColor redColor]];
(for now I have simple red color, later I want to had shadow, border ecc)
Because I want that this subview (with the 5 labels) is red (sure, the background must remain gradient).
When I run the application I see the background correctly but the view that I've set the color red is display totally black.
If in storyboard I change the background to Clear Color (in the view that should be red), the 5 labels come in and the background is the same of the main view.
I've placed a breakpoint to drawRect of the "red view" and this is called but nothing happen.
I'd suggest you take a look at Appearance Proxies. You can do what you're looking to do more easily with them.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
[MyCustomView appearance].backgroundColor = [UIColor clearColor];
...
}
Also, you likely shouldn't be adding sublayers in drawRect, especially if your views don't move or resize. I typically just add them in -(void)awakeFromNib.
Edit:
To address your question directly, I find the best way to create a gradient background in a custom view is to override +(Class)layerClass to provide a gradient layer. What this will do is make the fundamental layer for your custom view be whatever layer class you return. Typically this may be a shape layer for instance (for a solid color). In our case, we want a CAGradientLayer. Then in -(void)awakeFromNib you can set your gradient colors without mucking about with sublayers.
What I'm guessing is your original issue is that you're adding a gradient sublayer without setting start and end colors which will draw on top of your background color as black.
Example:
#implementation MyCustomView
+ (Class)layerClass {
return [CAGradientLayer class];
}
- (void)awakeFromNib {
CAGradientLayer* gradientLayer = (CAGradientLayer*)self.layer;
gradientLayer.locations = #[#0, #1];
gradientLayer.colors = #[(id)[UIColor redColor].CGColor, (id)[UIColor greenColor].CGColor];
}
Let me know if you need more details or this isn't working for you.

Why can't you change a UIView background color in drawRect?

In theory you can easily set the background color via:
self.backgroundColor = [UIColor redColor];
in drawRect, but this does does not have any effect. You can change the view's size, its borders, its subviews, etc., but not background Color. Similar SO queries suggest to add a subView or to change the backgroundColor in the initWithFrame or initWithCoder initialization messages. My question is why does it not work in drawRect when other characteristics can be changed?
Changing the view's layer backgroundColor also does nothing, perhaps for the same reason. What is the relationship between the view's backgroundColor and its layer's backgroundColor, since they are not the same?
If you set backgroundColor when the view is first configured (i.e., before the OS calls drawRect), that backgroundColor will be used without any further code on your part. As the UIView Class Reference says, the backgroundColor property is used to "set the view’s color rather than drawing that color yourself."
But if you attempt to set it in drawRect, the background fill has already been performed, so you won't immediately see the effect of changing the background color (unless you manually perform another fill yourself in drawRect). The drawRect method should be used for rendering the view, not for configuring it.
If the UIView sets the backgroundColor as the view is being configured (e.g. in whichever init method you avail yourself of), that background color will used, without any need to do anything else in drawRect.
As Rob stated in his answer, the background has already been drawn when the drawRect method is called. If you need to change the background color at this point, you will have to draw it yourself:
[self.backgroundColor setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
So you still can change the value of the property, but it will not be used until the view is drawn again.
Update for swift3 on drawRect function :
self.backgroundColor = UIColor.red
self.backgroundColor?.setFill()
UIGraphicsGetCurrentContext()!.fill(rect);
Set your background color into your awakeFromNib function:
// Objective-C
- (void)awakeFromNib {
self.backgroundColor = [UIColor redColor];
}
// Swift
override func awakeFromNib() {
backgroundColor = UIColor.red
}

Slow drawInRect with same image

I have a custom view inheriting from UIView.
In this view, there is an image which is always the same and I only want to draw on top of that.
However, in order for the image to be drawn, I have to call drawInRect every time the draw cycle runs.
- (void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect:self.bounds];
// drawing....
}
Is there a way to show the image and not to call this method every time drawRect is invoked?
Thank you.
Put the image in a UIImageView underneath your custom UIView instead of drawing it in drawRect:. Make your custom view's background color clearColor so the image shows through where drawRect: doesn't draw anything.
Don't try to make the image view a subview of your custom view. A subview always draws on top of its superview's content. You could make your custom view a subview of the image view, or you could make them siblings.
In the .h file of the custom class do this:
- (void) setup:
Then in the .m do this:
-(void) setup {
//drawing stuff
}
Then in the view did load of whatever the view controller that adds it as a subview is
[myCustomView setup];

Pattern to fill the entire screen

Every screen of my app has a common tint. Its not a background. Its a pattern that fills the entire screen and it is top of all the views. You can see the pattern flow continuously from one view to another inside the same screen. And it neither obscures other elements nor participate in event handling.
I tried implementing it with this code in my ViewController.
UIColor* texture = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Texture.png"]];
UIView* tintView = [[UIView alloc] initWithFrame:self.view.bounds];
[tintView setBackgroundColor:texture];
[tintView setAlpha:0.5];
[self.view addSubview:tintView];
But it doesn't pass on touches to the views behind it.
tintView shouldn't participate in any event handling. Rather it should let other elements behind it, handle the events like they do it normally.
Other way of doing it is set this as a background of the view property of a UIViewController and set a common alpha for all other subviews of view to show the pattern behind. That will be redundant in most ways.
Any better way of doing this?
Make your tintView a subclass of UIView and implement the hitTest:withEvent: method, returning nil. This will make your view transparent to touches. Or set userInteractionEnabled to NO.
Set the background color with a Textured image
UIImage *bgimg = [UIImage imageNamed:#"Texture.png"];
self.view.backgroundColor = [UIColor colorWithPatternImage:bgimg];

How to remove the "fuzzy" drop shadow in the UIPopoverController

I don't want the drop shadow when the UIPopoverController's view appears. Is there a way to remove this drop shadow look?
Not straight forward, but since iOS 5, you can make your own custom popover background using UIPopoverBackgroundView.
See the answer for this question: Using UIPopoverBackgroundView class. It's pointing to a good tuto.
Then, in the initWithFrame of your UIPopoverBackgroundView implementation, you can use a clearColor for the drop shadow. Using offset and radius did not work for me.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layer.shadowColor = [[UIColor clearColor] CGColor];
}
return self;
}
The shadow is an attribute of the popover view's layer. If you could get access to the layer, you could set it's shadow radius to 0.0 and shadow offset to {0.0, 0.0}. However, it looks like the view must be a private ivar of the popover controller, so there's not an easy way to get to it. Moreover, if you're looking to distribute this app through the app store, using a private ivar and changing the look of standard UI elements both are likely to get your app rejected.
You just have to use your custom UIPopoverBackgroundView and there implement this function:
+ (BOOL)wantsDefaultContentAppearance {
return NO;
}

Resources