I have a UIView on which I draw a UIBezierPath by finger.
When I rezoom the view (say after a path is drawn) a redraw function is triggered, which rescales the BezierPath:
- (void)redrawPathsWithScale:(float)scale
{
[_path applyTransform:CGAffineTransformMakeScale(scale, scale)];
[self setNeedsDisplay];
}
setNeedsDisplay causes drawRect to get called.
Now every time I zoom in to a absolute scale somewhere near x6 I immediately get a memory warning, and the App crashes.
My drawRect method looks like this:
- (void)drawRect:(CGRect)rect
{
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
The Curious thing is: Not implementing drawRect at all removes the memory warning. Implementing an empty drawRect still causes a crash!
Does adding [super drawRect:rect]; make any difference?
The Apple Documentation for drawRect states:
If you subclass UIView directly, your implementation of this method does not need to call super. However, if you are subclassing a different view class, you should call super at some point in your implementation.
If you're subclassing UIView, you should be fine but it might be worth checking this just in case.
Related
My goal is to create a custom UIButton subclass, that creates a round (or round rect) button with some additional features.
After some searching, I found that simply setting the cornerRadius of the button layer is the easiest way to make the button round:
#implementation MyRoundButton
-(id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame])
[self setupView];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if(self = [super initWithCoder:aDecoder])
[self setupView];
return self;
}
-(void)setupView {
self.layer.cornerRadius = MIN(self.frame.size.height, self.frame.size.width) / 2.0;
}
This works fine to, as long as I do not override drawRect:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
}
Of course I would like to add some custom drawing code to drawRect: but even if only [super drawRect:rect] the button is not round anymore: It drawn as rectangle within its bounds.
How is this possible? How can overriding a method with a simple super call change the behavior at all?
How can I avoid this problem? I already tried to let the layer unchanged and to simply draw the background manually in drawRect:. However the button draws its background rect anyway and my custom drawing is on top of it.
So, how to solve this?
The layer in your example conceptually contains a background with a corner radius, but the empty override of drawRect: causes that to be ignored. That's because, ordinarily, an instance of UIView depends on its built-in layer (an instance of CALayer) to render its content, so drawRect: isn't called. However, the layer is capable of delegating drawing behavior to its view, and will do so if you implement drawRect:.
If you're simply subclassing UIView, this shouldn't present any problem. However, subclassing a framework component such as UIButton is a bit dicier. One potential issue is the call to -[super drawRect:]; it's hard to know precisely what mischief that might cause, but that may be the source of the problem. By default, a UIButton doesn't need to do any custom drawing; it contains a nested instance of a private class, UIButtonLabel, that draws the button's title.
Instead of trying to override the drawing behavior of a class whose inner details are private, consider using one or more static images and simply setting the button's background image property for one or more states.
See if this changes anything...
-(void)setupView {
self.layer.cornerRadius = MIN(self.frame.size.height, self.frame.size.width) / 2.0;
self.clipsToBounds = YES;
}
I'm overriding drawRect: in one of my views and it works even without calling [super drawrect:rect]. How does that work?
- (void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeDestinationOver);
[[UIColor clearColor] setFill];
UIRectFill(CGRectMake(0,0,100,100));
}
From the documentation:
"The default implementation of this method does nothing.Subclasses
that use technologies such as Core Graphics and UIKit to draw their
view’s content should override this method and implement their drawing
code there"
Also from the documentation..
"If you subclass UIView directly, your implementation of this method
does not need to call super. However, if you are subclassing a
different view class, you should call super at some point in your
implementation."
Link to the documentation
I am assuming, your super class is UIView. Hence it works even if [super drawrect:rect] is not called. Because the drawRect: because UIView does nothing. So it makes no difference in this case whether or not you call [super drawrect:rect].
Basically, it works because the machinery that sets up the graphics context for drawing, etc, doesn't live in the UIView implementation of -drawRect: (which is empty).
That machinery does live somewhere, of course, let's pretend in every UIView there's some internal method like this-- names of methods, functions, and properties are invented to protect the innocent:
- (void)_drawRectInternal:(CGRect)rect
{
// Setup for drawing this view.
_UIGraphicsContextPushTransform(self._computedTransform);
// Call this object's implementation of draw (may be empty, or overridden).
[self drawRect:rect];
// draw our subviews:
for (UIView * subview in [self subviews]) {
[subview _drawRectInternal:rect];
}
// Teardown for this view.
_UIGraphicsContextPopTransform();
}
This is a cleaner way of building the frameworks than relying on a subclasser to do the right thing in a common override, where getting it wrong would have result in very puzzling bad behavior. (And this wouldn't really be possible at all since the draw might need a setup and a teardown step.)
I'm sure subview doesn't work without calling [super drawrect:rect].If you need to keep the superview's drawing ,you must add [super drawrect:rect] in the subview's drawrect:rect,or you'll replace the drawing.
Thanks.. There is no need to call super, because the superclass here is UIView, whose drawRect: does nothing.
In general, we call setNeedDisplay if we want recall drawRect for drawing.
I'm quitely new for the iOS, so I don't know much of it.
I know how to make UIView (and its childviews) but I don't know about drawRect
I make a class which inherits from UIView and make subviews in initWithFrame method.
I want to draw a NSString using CGContext after adding subViews and move it outside of view after 5 seconds.
Could anyone explain when drawRect is called and how to move it?
This code helps you to draw a NSString which was stored before in _content:
- (void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
[_content drawInRect:rectForFrame
withFont:[UIFont fontWithName:kFontMedium size:15.0f]
lineBreakMode:NSLineBreakByWordWrapping
alignment:NSTextAlignmentCenter];
}
To let the element slide out, you would have to call a NSTimer after your UIView was shown (Maybe with a delay using performSelector:withObject:afterDelay:) and change the attributes of the CGRect you want to write the NSString inside. You can just setup a method which will be called by your NSTimer in a specific interval which also updates the UIView.
- (void)updateView{
//create a rect
rectForFrame = CGRectMake....;
[self setNeedsDisplay];
}
One way to do it would be to draw the string and then start an NSTimer. Start a 5 second timer, and when it fires, then you can move it.
As for when -drawRect: is called - it's called whenever your view needs to be updated. It will be called when your view is first displayed by the OS. After that, it will usually be called when it changes size or shape, or when your code calls [myView setNeedsDisplay:YES], which tells the OS to update it.
I have a ViewController on storyboard. I have used the interface builder to set a toolbar at the bottom of the screen. I have set the custom view to a view overrides drawRect. However, for the life of me, I cannot get anything ever to show up on screen called from that drawRect. drawRect itself is called just fine, but nothing shows up on screen.
Also, I have this ViewController with a method that uses AVCaptureSession to toggle its background to a live view from camera input. I had suspected that this might have been the cause for error, but after removing all references of AVCaptureSession, I still cannot get this to work.
Sorry for my writing and/or lack of logic, I don't have any sleep right now.
Edit: Here is a small example of code that won't work. Every method inside gets called, but nothing is to show.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, 0,0); //start at this point
CGContextAddLineToPoint(context, 100, 100); //draw to this point
// and now draw the Path!
CGContextStrokePath(context);
}
The documentation for UIView says to not call [super drawRect] if you're overriding UIView. Remove that call! It's been known to cause strange behaviour.
According to the docs:
If you subclass UIView directly, your implementation of this method
does not need to call super. However, if you are subclassing a
different view class, you should call super at some point in your
implementation.
I don't really understand how CALayer's display and drawInContext relate to drawRect in the view.
If I have an NSTimer that sets the [self.view setNeedsDisplay] every 1 second, then drawRect is called every 1 second, as shown by an NSLog statement inside of drawRect.
But if I subclass a CALayer and use that for the view, if I make the display method empty, then now drawRect is never called. Update: But display is called every 1 second, as shown by an NSLog statement.
If I remove that empty display method and add an empty drawInContext method, again, drawRect is never called. Update: But drawInContext is called every 1 second, as shown by an NSLog statement.
What is exactly happening? It seems that display can selectively call drawInContext and drawInContext can selectively somehow call drawRect (how?), but what is the real situation here?
Update: there is more clue to the answer:
I changed the CoolLayer.m code to the following:
-(void) display {
NSLog(#"In CoolLayer's display method");
[super display];
}
-(void) drawInContext:(CGContextRef)ctx {
NSLog(#"In CoolLayer's drawInContext method");
[super drawInContext:ctx];
}
So, let's say, if there is a moon (as a circle drawn by Core Graphics) at location (100,100) in the View, and now I change it to location (200,200), naturally, I will call [self.view setNeedsDisplay], and now, CALayer will have no cache at all for the new view image, as my drawRect dictates how the moon should now be displayed.
Even so, the entry point is CALayer's display, and then CALayer's drawInContext: If I set a break point at drawRect, the call stack shows:
So we can see that CoolLayer's display is entered first, and it goes to CALayer's display, and then CoolLayer's drawInContext, and then CALayer's drawInContext, even though in this situation, no such cache exist for the new image.
Then finally, CALayer's drawInContext calls the delegate's drawLayer:InContext. The delegate is the view (FooView or UIView)... and drawLayer:InContext is the default implementation in UIView (as I did not override it). It is finally that drawLayer:InContext calls drawRect.
So I am guessing two points: why does it enter CALayer even though there is no cache for the image? Because through this mechanism, the image is drawn in the context, and finally returns to display, and the CGImage is created from this context, and then it is now set as the new image cached. This is how CALayer caches images.
Another thing I am not quite sure is: if [self.view setNeedsDisplay] always trigger drawRect to be called, then when can a cached image in CALayer be used? Could it be... on Mac OS X, when another window covers up a window, and now the top window is moved away. Now we don't need to call drawRect to redraw everything, but can use the cached image in the CALayer. Or on iOS, if we stop the app, do something else, and come back to the app, then the cached image can be used, instead of calling drawRect. But how to distinguish these two types of "dirty"? One is a "unknown dirty" -- that the moon needs to be redrawn as dictated by the drawRect logic (it can use a random number there for the coordinate too). The other types of dirty is that it was covered up or made to disappear, and now needs to be re-shown.
When a layer needs to be displayed and has no valid backing store (perhaps because the layer received a setNeedsDisplay message), the system sends the display message to the layer.
The -[CALayer display] method looks roughly like this:
- (void)display {
if ([self.delegate respondsToSelector:#selector(displayLayer:)]) {
[[self.delegate retain] displayLayer:self];
[self.delegate release];
return;
}
CABackingStoreRef backing = _backingStore;
if (!backing) {
backing = _backingStore = ... code here to create and configure
the CABackingStore properly, given the layer size, isOpaque,
contentScale, etc.
}
CGContextRef gc = ... code here to create a CGContext that draws into backing,
with the proper clip region
... also code to set up a bitmap in memory shared with the WindowServer process
[self drawInContext:gc];
self.contents = backing;
}
So, if you override display, none of that happens unless you call [super display]. And if you implement displayLayer: in FooView, you have to create your own CGImage somehow and store it in the layer's contents property.
The -[CALayer drawInContext:] method looks roughly like this:
- (void)drawInContext:(CGContextRef)gc {
if ([self.delegate respondsToSelector:#selector(drawLayer:inContext:)]) {
[[self.delegate retain] drawLayer:self inContext:gc];
[self.delegate release];
return;
} else {
CAAction *action = [self actionForKey:#"onDraw"];
if (action) {
NSDictionary *args = [NSDictionary dictionaryWithObject:gc forKey:#"context"];
[action runActionForKey:#"onDraw" object:self arguments:args];
}
}
}
The onDraw action is not documented as far as I know.
The -[UIView drawLayer:inContext:] method looks roughly like this:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)gc {
set gc's stroke and fill color spaces to device RGB;
UIGraphicsPushContext(gc);
fill gc with the view's background color;
if ([self respondsToSelector:#selector(drawRect:)]) {
[self drawRect:CGContextGetClipBoundingBox(gc)];
}
UIGraphicsPopContext(gc);
}
The update procedure of UIView is based on the dirty state meaning the view is not likely to be redrawn if there's no change at it's appearance.
That is internal implementation mentioned at the developer reference.
Implementing a drawInContext or display or drawRect tells the OS which one you want called when the view is dirty (needsDisplay). Pick the one you want called for a dirty view and implement that, and don't put any code you depend on getting executed in the others.