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.
Related
I am working on multiple terminal screen app, for that I have a custom UIView subclass for the terminal views. Every time I need a new terminal screen, I prepare a new view.
This view class draws the text using a CGContextRef. The problem I am facing is that the context only draws the text of the last view that was created, e.g. if I have 3 terminals and drawing on first/second, it still draws on the third one.
My code so far:
-(void)drawRect:(CGRect)rect{
contxt = UIGraphicsGetCurrentContext();
}
-(void)setNeedsDisplayInRect:(CGRect)rect{
UIGraphicsPushContext(contxt);
//CGContextSaveGState(contxt);
CGContextSetTextMatrix(contxt,CGAffineTransformIdentity);
if (translated) {
CGContextScaleCTM(contxt, 1, -1);
translated = NO;
}
CGRect rectConvert = CGRectMake(rect.origin.x, rect.origin.y-screenWindowHeight, rect.size.width, rect.size.height);
CGContextSetFillColorWithColor(contxt, bgColor.CGColor);
CGContextFillRect(contxt, rectConvert);
if (!isDeleteChar) {
CGContextSetFillColorWithColor(contxt, fgColor.CGColor);
[displayString drawInRect:rectConvert withFont:font lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentLeft];
}
if (ul == EXTENDED_5250_UNDERLINE) {
CGContextSetFillColorWithColor(contxt, fgColor.CGColor);
[#"_" drawInRect:rectConvert withFont:font lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentLeft];
}
//CGContextRestoreGState(contxt);
UIGraphicsPopContext();
}
Finally I solved it by own using
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]]; just after setNeedsDisplay.
First and foremost, you should not be doing drawing in the -setNeedsDisplayRect: method, all of your drawing code should be in drawRect: instead. This way, the main runloop can better organize redrawing of the views.
Second, I suspect the variables that you are using for your CGRect conversions are faulty and are drawing outside of the view bounds. You can test this premise by clipping the view's drawing (set layer.masksToBounds to YES for the views)
If this is the case, you can adjust them to be relative to the view (all drawing within the view should be relative to its bounds, not its frame). When drawing the view, assume a canvas that stretches the bounds property of the view, i.e origin at (0,0) and size of (width,height).
Now, it is worth also discussing what the rect property passed to drawRect: really is, it is not guaranteed to be the entire bounds of the view, so you should not assume that. It is the portion of the view that needs to be redrawn, however, common practice (for simpler views) is to ignore that parameter and just redraw the entire view. Once this becomes too expensive (or you need the drawing to be more optimal), you can look into doing partial redraws of your view.
All in all, it is difficult to diagnose the full problem without seeing the entire UIView subclass code.
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 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 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.
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.