UIView & Graphics Context - iphone

The UIView class does not set a graphics context, so when I go to get the current context, it comes back as nil. I'm in a method subclassed off of UIView, how may I get the graphics context to be able to simply draw a line? Very new at the x-code game. Any help would be appreciated.

The graphics context is only set in the drawRect: method which you will need to override, then do all your drawing in there. As a word of caution do not call drawRect: directly, it will be called automatically when the UIView needs to be displayed. If you want to force a draw send a setNeedsDisplay message to your UIView.

Override the - (void)drawRect:(CGRect)rect method in your UIView subclass
http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-BBCDGJHF
According to the docs:
You can get a reference to the
graphics context using the
UIGraphicsGetCurrentContext function,
but do not retain the graphics context
because it can change between calls to
the drawRect: method.
It sets it up for you before invoking that method.

Related

On iOS, if a subclass of CALayer is used with an empty display method, then app seems to go into infinite loop?

If a Single View app is created, with a FooView that subclasses UIView, and do a
NSLog(#"hello");
in drawRect, then it is printed.
And if I create a subclass of CALayer called CoolLayer, and add this method to FooView.m:
+(Class) layerClass {
return [CoolLayer class];
}
and at the end of FooView.m's drawRect, do a
NSLog(#"layer's class is %#", self.layer.class);
then CoolLayer is printed. So now the view's underlaying layer is CoolLayer.
But when the following is added to CoolLayer.m:
-(void) display {
}
which is the method that is automatically called to redraw the layer (similar to drawRect), then no NSLog whatsoever was printed. It might be that the app went into an infinite loop. (even my touchesBegan that prints out NSLog messages is not printing). If a breakpoint is set at display, it will stop there once but when I continue the program, it will never arrive at display again. What is wrong with this and how can it be fixed?
The layer's display method will not be called again unless the layer is dirty, i.e. set to need a redisplay. This is usually a good thing, and is why you don't see the method being called more than once.
Also, the normal implementation of display will call the drawInContext: method. Since you override this in your subclass, the drawRect: method of the view is never called. You need to either replicate the standard behavior of CALayer, or call the superclass' display method in your own implementation.
That does not sound like an infinite loop. If you were in an infinite loop your app would freeze, and after a few seconds the springboard app would kill it for being unresponsive.
Call setNeedsDisplay or setNeedsDisplayInRect on your layer to make it "dirty" and require drawing again. Note that you don't want to call setNeedsDisplay any more than you have to, because it takes a lot of work to re-render the layer and push it's contents onto the screen. Only display when something has changed
You're not seeing an infinite loop here. If you were, as Duncan points out, you'd eventually crash either due to the watchdog timer or from an infinite recursion that would be immediately obvious in the stack trace you'd see in the debugger.
If you put an NSLog into your UIView's -drawRect: method, then override its default layer class with your own custom CALayer that does its own drawing, your UIView's -drawRect: would no longer be called. Drawing would now be handled by your custom backing layer.
As described in the "Providing CALayer Content by Subclassing" subsection of the Core Animation Programming Guide, you typically override -display in your CALayer if you want to somehow customize the contents CGImageRef for your layer. Normally, you'll be overriding -drawInContext: if you want to render custom Quartz drawing within your CALayer. Making your overridden -display method totally blank, and not writing anything to the contents property, is not standard behavior, so I'm not surprised that you're seeing odd results from doing that.
Based on your series of recent questions, I highly recommend you stop and spend some time reading the Core Animation Programming Guide and looking at some sample code involving CALayers before proceeding further.

iOS: Using UIView's 'drawRect:' vs. its layer's delegate 'drawLayer:inContext:'

I have a class which is a subclass of UIView. I am able to draw stuff inside the view either by implementing the drawRect method, or by implementing drawLayer:inContext: which is a delegate method of CALayer.
I have two questions:
How to decide which approach to use? Is there a use case for each one?
If I implement drawLayer:inContext:, it is called (and drawRect isn't, at least as far as putting a breakpoint can tell), even if I don't assign my view as the CALayer delegate by using:
[[self layer] setDelegate:self];
how come the delegate method is called if my instance is not defined to be the layer's delegate? and what mechanism prevents drawRect from being called if drawLayer:inContext: is called?
How to decide which approach to use? Is there a use case for each one?
Always use drawRect:, and never use a UIView as the drawing delegate for any CALayer.
how come the delegate method is called if my instance is not defined to be the layer's delegate? and what mechanism prevents drawRect from being called if drawLayer:inContext: is called?
Every UIView instance is the drawing delegate for its backing CALayer. That's why [[self layer] setDelegate:self]; seemed to do nothing. It's redundant. The drawRect: method is effectively the drawing delegate method for the view's layer. Internally, UIView implements drawLayer:inContext: where it does some of its own stuff and then calls drawRect:. You can see it in the debugger:
This is why drawRect: was never called when you implemented drawLayer:inContext:. It's also why you should never implement any of the CALayer drawing delegate methods in a custom UIView subclass. You should also never make any view the drawing delegate for another layer. That will cause all sorts of wackiness.
If you are implementing drawLayer:inContext: because you need to access the CGContextRef, you can get that from inside of your drawRect: by calling UIGraphicsGetCurrentContext().
drawRect should only be implemented when absolutely needed. The default implementation of drawRect includes a number of smart optimizations, like intelligently caching the view's rendering. Overriding it circumvents all of those optimizations. That's bad. Using the layer drawing methods effectively will almost always outperform a custom drawRect. Apple uses a UIView as the delegate for a CALayer often - in fact, every UIView is the delegate of it's layer. You can see how to customize the layer drawing inside a UIView in several Apple samples including (at this time) ZoomingPDFViewer.
While the use of drawRect is common, it's a practice that has been discouraged since at least 2002/2003, IIRC. There aren't many good reasons left to go down that path.
Advanced Performance Optimization on iPhone OS (slide 15)
Core Animation Essentials
Understanding UIKit Rendering
Technical Q&A QA1708: Improving Image Drawing Performance on iOS
View Programming Guide: Optimizing View Drawing
Here're codes of Sample ZoomingPDFViewer from Apple:
-(void)drawRect:(CGRect)r
{
// UIView uses the existence of -drawRect: to determine if it should allow its CALayer
// to be invalidated, which would then lead to the layer creating a backing store and
// -drawLayer:inContext: being called.
// By implementing an empty -drawRect: method, we allow UIKit to continue to implement
// this logic, while doing our real drawing work inside of -drawLayer:inContext:
}
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
...
}
Whether you use drawLayer(_:inContext:) or drawRect(_:) (or both) for custom drawing code depends on whether you need access to the current value of a layer property while it is being animated.
I was struggling today with various rendering issues related to these two functions when implementing my own Label class. After checking out the documentation, doing some trial-and-error, decompiling UIKit and inspecting Apple's Custom Animatable Properties example I got a good sense on how it's working.
drawRect(_:)
If you don't need to access the current value of a layer/view property during its animation you can simply use drawRect(_:) to perform your custom drawing. Everything will work just fine.
override func drawRect(rect: CGRect) {
// your custom drawing code
}
drawLayer(_:inContext:)
Let's say for example you want to use backgroundColor in your custom drawing code:
override func drawRect(rect: CGRect) {
let colorForCustomDrawing = self.layer.backgroundColor
// your custom drawing code
}
When you test your code you'll notice that backgroundColor does not return the correct (i.e. the current) value while an animation is in-flight. Instead it returns the final value (i.e. the value for when the animation is completed).
In order to get the current value during the animation, you must access the backgroundColor of the layer parameter passed to drawLayer(_:inContext:). And you must also draw to the context parameter.
It is very important to know that a view's self.layer and the layer parameter passed to drawLayer(_:inContext:) are not always the same layer! The latter might be a copy of the former with partial animations already applied to its properties. That way you can access correct property values of in-flight animations.
Now the drawing works as expected:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
But there are two new issue: setNeedsDisplay() and several properties like backgroundColor and opaque do no longer work for your view. UIView does no longer forward calls and changes to its own layer.
setNeedsDisplay() does only do something if your view implements drawRect(_:). It doesn't matter if the function actually does something but UIKit uses it to determine whether you do custom drawing or not.
The properties likely don't work anymore because UIView's own implementation of drawLayer(_:inContext:) is no longer called.
So the solution is quite simple. Just call the superclass' implementation of drawLayer(_:inContext:) and implement an empty drawRect(_:):
override func drawLayer(layer: CALayer, inContext context: CGContext) {
super.drawLayer(layer, inContext: context)
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
override func drawRect(rect: CGRect) {
// Although we use drawLayer(_:inContext:) we still need to implement this method.
// UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer.
}
Summary
Use drawRect(_:) as long as you don't have the problem that properties return wrong values during an animation:
override func drawRect(rect: CGRect) {
// your custom drawing code
}
Use drawLayer(_:inContext:) and drawRect(_:) if you need to access the current value of view/layer properties while they are being animated:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
super.drawLayer(layer, inContext: context)
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
override func drawRect(rect: CGRect) {
// Although we use drawLayer(_:inContext:) we still need to implement this method.
// UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer.
}
On iOS, the overlap between a view and its layer is very large. By default, the view is the delegate of its layer and implements the layer's drawLayer:inContext: method. As I understand it, drawRect: and drawLayer:inContext: are more or less equivalent in this case. Possibly, the default implementation of drawLayer:inContext: calls drawRect:, or drawRect: is only called if drawLayer:inContext: is not implemented by your subclass.
How to decide which approach to use? Is there a use case for each one?
It doesn't really matter. To follow the convention, I would normally use drawRect: and reserve the use of drawLayer:inContext: when I actually have to draw custom sublayers that are not part of a view.
The Apple Documentation has this to say: "There are also other ways to provide a view’s content, such as setting the contents of the underlying layer directly, but overriding the drawRect: method is the most common technique."
But it doesn't go into any details, so that should be a clue: don't do it unless you really want to get your hands dirty.
The UIView's layer's delegate is pointed at the UIView. However, the UIView does behave differently depending on whether or not drawRect: is implemented. For example, if you set the properties on the layer directly (such as its background color or its corner radius), these values are overwritten if you have a drawRect: method - even if its completely empty (i.e. doesnt even call super).

iPhone: Resizing in drawRect:

I have a UIView based control which I need to resize depending on various criteria and properties of the control. I'm not sure if the way I'm doing it is the best way but so far it's the only one I've found that seems to work. Because the size of the control is dependant on various properties, I cannot set the size in a constructor.
I have a method called setupControl: which contains all the code to finish the setup based on the properties set. I don't want setupControl: called manually, so in drawRect I detect if I need to call it and then queue a selector like this:
[self performSelector:#selector(setupControl)withObject:self afterDelay:0];
return;
At the bottom of setupControl: I then do:
[self setNeedsDisplay];
self.hidden = NO;
I've also overridden the initWithFrame: and initWithDecoder: constructors so that they set the UIView to be hidden to start with until the setup code is executed. The idea being to eliminate any "flashes" on the display as the control resizes.
As I said this works fine, the controls can be drawn ay size is the xib file and then set themselves to the correct size at run time.
My question is whether this method of queuing a selector, exiting the drawRect: and then using a setNeedsDisplay is the only way to do this? O is there some method I haven't found that I can override? or something else?
What you probably want to override is layoutSubviews rather than drawRect as you're changing the layout, not implementing custom drawing.
You may also have to use a custom setter for any properties that change the size of the view and call [self setNeedsLayout] in the setter to make sure your layoutSubviews method is called before your view size is computed.

iPhone - setNeedsDisplay doesn't call drawRect

I have seen many posts about this problem but didn't get an answer. I have a controller which view is added to the main window. The controller's view has a subview which has a drawRect. The problem is that this function is never called even if I call [self setNeedsDisplay].
Thanks
It is -(void)drawRect:(CGRect)rect right? Make sure the method signature is correct, and you don't omit the rect argument even if you don't use it.
-setNeedsDisplay should be called the the subview, not self.
Also, -setNeedsDisplay won't call -drawRect: immediately. It only flushes the graphics cache so that -drawRect: is forced to be called in the next update of the frame.

CALayer delegate method drawLayer not getting called

My object a sub class of NSObject has CALayer instance variable. I use it for drawing and caching content with its delegate set to my object.
But for some reason drawLayer:inContext: method NEVER gets called. Whereas actionForLayer:forKey: delegate method does get called implying the delegate is getting set properly with layer.delegate = self in the object's init method.
Any suggestions on what is preventing my layer drawing method drawLayer:inContext: from getting called ?
I am called the [layer setNeedDisplay] often. So I guess it is some fundamental error.
drawLayer:inContext: won't get called if your frame is CGRectZero or offscreen. Also, if your CALayer isn't attached to an existing onscreen layer, it will never draw, no matter how many times you call setNeedsDisplay
Implement an empty drawRect:
- (void)drawRect:(CGRect)rect {
}
Taken from the ZoomingPDFViewer project:-
UIView uses the existence of -drawRect: to determine if it should
allow its CALayer to be invalidated, which would then lead to the
layer creating a backing store and -drawLayer:inContext: being
called. By implementing an empty -drawRect: method, we allow UIKit
to continue to implement this logic, while doing our real drawing work
inside of -drawLayer:inContext:
The layer object's setNeedsDisplay must be called. Simply adding the layer as a sublayer does not do that for you. Got this from Ray Wenderlich's CALayer tutorial.
If you're eventually using the CALayer with a UIView then the delegate must be the view object itself:
From the iOS CALayer documentation:
"In iOS, if the layer is associated with a UIView object, this property must be set to the view that owns the layer."
If you have a multi-threaded app where background processing drives the need to update the CALayer, you must call setNeedsDisplay in the main thread