UIView subclass draws background despite completely empty drawRect: - why? - objective-c

So, I have a custom UIView subclass which enables drawing of rounded edges. The thing draws perfectly, however the background always fills the whole bounds, despite clipping to a path first. The border also draws above the rectangular background, despite the fact that I draw the border in drawRect: before the background. So I removed the whole content of drawRect:, which is now virtually empty - nevertheless the background gets drawn!
Anybody an explanation for this? I set the backgroundColor in Interface Builder. Thanks!

Sorry for this monologue. :)
The thing is that the UIView's layer apparently draws the background, which is independent from drawRect:. This is why you can't get rid of the background by overriding drawRect:.
You can either override - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx and make the layer draw whatever you want, or you override - (void) setBackgroundColor:(UIColor *)newColor and don't assign newColor to backgroundColor, but to your own ivar, like myBackgroundColor. You can then use myBackgroundColor in drawRect; to draw the background however you like.
Overriding setBackgroundColor:
Define an instance variable to hold your background color, e.g. myBackgroundColor. In your init methods, set the real background color to be the clearColor:
- (id) initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super init...])) {
[super setBackgroundColor:[UIColor clearColor]];
}
return self;
}
Override:
- (void) setBackgroundColor:(UIColor *)newColor
{
if (newColor != myBackgroundColor) {
[myBackgroundColor release];
myBackgroundColor = [newColor retain];
}
}
Then use myBackgroundColor in your drawRect: method. This way you can use the color assigned from Interface Builder (or Xcode4) in your code.

Here's an easier fix:
self.backgroundColor = [UIColor clearColor];

I hesitate to suggest this because I don't want to offend you, but have you set opaque to NO?

This is easily reproducible:
Create a new View-Based iPhone Application. Create an UIView subclass and leave the drawRect: method completely empty. In Interface Builder, drag a UIView into the main view, assign it a background color and set the class of the view to your subclass. Save, build and run, and see, the view shows the background color.
I have circumvented this behaviour by overriding setBackgroundColor: and assigning the color to my own ivar, always leaving the backgroundColor property nil. This works, however I'm still wondering why this works this way.

You should set
clearsContextBeforeDrawing = NO
It's that simple.
From the UIView Reference Docs
Oops. I misunderstood the question. The OP (original poster) wants their custom control to support the standard "backgroundColor" property (eg, set by the gui designer), but does NOT want the system to paint that color. The OP wants to paint the bg himself so that he can round the corners.
In that case, the post about overriding the layer level drawing is correct.
The solution I posted will prevent the UI system from clearing your buffer before drawing. When you have no bg defined, if clearsContextBeforeDrawing is set, the iOS will clear your view's buffer, setting all pixels to transparent black. Doing this for a full-screen view takes about 5ms in an iPad3, so it's not free (pushing that 2048x1536 pixels never is). For comparison drawing a full-screen bitmap (using kCGBlendModeCopy to force blitting) takes ~25ms (using quartz, not GPU).

While it is possible to override the code that copies the background colour into the view's layer, I'm not a fan of this approach. I believe it can cause issues with views intended to be opaque. My suggestion is to create a custom colour variable like this:
#IBInspectable var foregroundColor: UIColor = .black {
didSet { setNeedsDisplay() }
}
Now, set your view's backgroundColor to clear and use foregroundColor in your drawing code in its place. The code above takes care of updating it when the value changes, and also exposes it to Interface Builder if required.
Another alternative is to use your view's tintColor instead, and to ensure that it updates correctly when tint colour changes. You may be reserving tint colour for other uses though, so this is not necessarily ideal. If you use tintColor, don't forget to include the following:
override func tintColorDidChange() {
setNeedsDisplay()
}

Related

Layer-Backed NSControl Still Calls NSCell Drawing Routines

Context:
Apple has "soft-deprecated" NSCell on macOS. I'm trying to reduce the use of it in my custom NSControl subclasses and, instead, use CALayers to handle my drawing. To do this, I make my NSButton subclass layer-backed:
self.wantsLayer = YES;
And then I attempt to handle my drawing using the "updateLayer" path instead of "drawRect":
- (BOOL) wantsUpdateLayer {
return YES;
}
- (void) updateLayer {
// Change layer background/border colors if pressed, disabled, etc.
// Change text color of a CATextLayer sublayer for button's title.
}
The Problem:
If I use -updateLayer, the NSCell still receives drawing commands and draws its title, resulting in a "blurry" title string in my control because the string is being drawn twice—once in my textLayer and once by the Cell.
If, however, I do the exact same work as above in -drawRect: instead of -updateLayer, then the cell does NOT do any drawing (because I don't call down into it with [self.cell drawInteriorWithFrame:ControlView:])
I cannot, for the life of me, figure out what's firing the cell drawing when the associated controlView (my button subclass) has opted into -updateLayer instead of -drawRect:.
I have looked at all the methods on NSControl.h and NSView.h. I've overridden all of the -updateCell and associated methods. None of those are the culprit. I cannot simply set self.cell=nil because NSControl still relies on NSCell for event handling, etc.
Can someone tell me how to stop NSCell from drawing when its associated controlView is using -updateLayer instead of -drawRect:?
In -[NSButtonCell layoutLayerWithFrame:inView:], the cell adds a NSTextField to draw the title. Override titleRectForBounds: and return NSZeroRect to remove the title.

UIView animations - When does it animate certain properties like scale and position?

I'm currently working with a UITableViewController which contains some UITableViewCells subclasses.
When layoutSubviews is called on these UITableViewCells, I change some of the cells' subviews' scales and positions depending on the width and height of the contentView. (The UITableViewCells have some subviews, and I change their scales and positions)
A good example is toggling edit mode, since it shortens the contentView by a bit.
When I do this, the scales and positions of my UITableViewCell's subview do animate
func toggleEdit() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.35)
self.tableView.beginUpdates()
self.tableView.editing = !self.tableView.editing
self.tableView.endUpdates()
UIView.commitAnimations()
}
When I do this, ONLY the positions animate, the scales change immediately which looks really ugly:
func toggleEdit() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.35)
self.tableView.editing = !self.tableView.editing
UIView.commitAnimations()
}
I kind of found this out by accident, so I'm wondering now what kind of magic begin/end tableViewUpdate does, and how I can control myself in any scenario which properties should animate and which shouldn't.
The docs state
When you call endUpdates, UITableView animates the operations simultaneously
You could try changing things that you don't want animated in a performWithoutAnimation(_:) block
UIView.performWithoutAnimation {
// things you don't want to animate
}

How to change background color for NSview in Cocoa

I want to change background color for many nsview. I override drawRect: on subclass NSview but i don't know how to set background color for myview( is reference IBOUTLET). please help me. Thanks so much
Code for CustomView.h
#import <Cocoa/Cocoa.h>
#interface CustomView : NSView
#end
Code for CustomView.m
#import "CustomView.h"
#implementation CustomView
- (void) drawRect:(NSRect)dirtyRect {
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
#end
And in main class, i added #import "CustomView.h" but i don't know how to set background for myview.
Welcome to Cocoa drawing.
Cocoa drawing uses Quartz which is a PDF model.
Drawing in this occurs in a back to front procedural order.
In Quartz drawing there is a drawing environment state object called the Graphics Context.
This is an implicit object in many of the drawing ops in AppKit.
(in Core Graphics or other APIs it could need to be explicitly called)
You tell the Graphics Context what the current color and other parameters are, then draw something, then change parameters and draw more, etc...
In AppKit, you do this by sending a message to the NSColor object, which is weird. but that's how it works.
In your drawRect: method you should call super first usually, because you probably want your drawing on top of that...
- (void) drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// This next line sets the the current fill color parameter of the Graphics Context
[[NSColor whiteColor] setFill];
// This next function fills a rect the same as dirtyRect with the current fill color of the Graphics Context.
NSRectFill(dirtyRect);
// You might want to use _bounds or self.bounds if you want to be sure to fill the entire bounds rect of the view.
}
If you want to change the color, you'll need an #property NSColor
You might need more than one for your drawing.
That allows you to set the color.
You might want the view to use KVO and observe its own color property then draw itself if the color property changes.
You could do a lot of different things to set the color. (a button or pallette elsewhere) But all of them would eventually result in sending a message to set the color of a property of your view for drawing.
Finally, if you want to update the drawing, you need to call [myView setNeedsDisplay:YES]; where myView is a reference to an instance of the NSView subclass.
There is also display but that's forceful.
setNeedsDisplay: says to schedule it on the next run of the event loop (runLoop). display kind of makes everything jump to that right away.
The event loop comes back around fast enough you shouldn't force it.
Of note, setNeedsDisplay: is the entire view.
In a fancy ideal world with complex views, you might want to more appropriately optimize things by calling setNeedsDisplayInRect: where you designate a specific CG/NSRect of the view as needing to be redrawn.
This allows the system to focus redrawing to the smallest union rect possible in the window.
I'm super late, but this is how I do it - there's no need to sub class:
NSView *myview = [NSView new];
[view setWantsLayer:YES];
view.layer.backgroundColor = [NSColor greenColor].CGColor;

How to change the background color of an NSPopupButton?

I am trying to tackle a problem which sounds pretty simple: changing the background color of an NSPopupButton.
Interface Builder only allows changing the style to a pre-defined one and doesn't allow changing the background color. Also, setting up an IBOutlet didn't help since NSPopupButton doesn't have a setBackgroundColor method.
I also tried subclassing NSPopupButton to override the drawRect method. Here's what I have tried:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] setFill];
NSRectFill(dirtyRect);
}
This draws a red rectangle over the NSPopupButton rather than setting it as a background color.
Any ideas on how to go about solving this?
You should create a subclass of NSPopUpButtonCell, then override
- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
NSPopupButtonCell is a subclass of NSButtonCell which defines several methods for drawing individual cell components, eg bezel, title, image.
You can then expand the NSPopupButton and change its cell subclass to your new subclass and it should use your drawing methods.
Cocoa primarily uses NSCell to handle drawing, unlike iOS
Swift version of #DanBrooker's answer. The example shows setting a background color
class PopUpButtonCell: NSPopUpButtonCell {
override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
guard let context = NSGraphicsContext.current?.cgContext else { return }
NSColor.red.setFill() // NSColor.white.setFill()
context.fill(frame)
}
}
The button is draw by the system, so there isn't a real way to set the background color in a way that the system draws it like you want.The only thing that you can do is to draw it in the drawRect method, also drawing the title, and drawing a portion of the rectangle.

How is backgroundColor handled for UIView in Objective_C?

I have been playing with some of Apple's example code for customizing UITableViewCells. I have run into some weird behavior that has left me completely confused about how backgroundColor works.
The following code is a much reduced version of Apple's example custom UIView within custom UITableViewCell. The init function sets the background color to purple and then the drawRect sets the background color to green. I would expect to never see purple, but that is all I see. Through NSLog statements I know that the init method is being called for each of the cells, followed by drawRect being called for each of the cells. The green setting seems to be ignored. If I call [self setNeedsDisplay] any time after the initial load, the background is correctly set to green.
- (id)initWithFrame:(CGRect)frame {
counter = 0;
if ((self = [super initWithFrame:frame])) {
self.opaque = YES;
self.backgroundColor = [UIColor purpleColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
self.backgroundColor = [UIColor greenColor];
}
Can anyone explain to me why this would be happening like this?
Your -drawRect: implementation doesn't actually do any drawing, so it's not going to change anything itself.
Presumably, the drawing is being done by a layer associated with your view. It looks like, in your environment, the background-colored layer is doing its drawing before your -drawRect: method is called. When the layer redraws, your -drawRect: method also happens to be called. So, after you change the background color, you don't see any changes till the background layer draws again, which you can tell happened because your -drawRect: gets called again.
If you want the color change to apply immediately, you need to mark your view as needing display, so that it will completely redraw with the changed background color.
Could you post the code for cellForRowAtIndexPath ??
Why did you not try using
cell.textLabel.textColor = [UIColor purpleColor];