0

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?

Jeremy L
  • 3,770
  • 6
  • 41
  • 62

3 Answers3

2

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

Duncan C
  • 128,072
  • 22
  • 173
  • 272
2

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.

Svein Halvor Halvorsen
  • 2,474
  • 1
  • 16
  • 14
  • That's really strange: if my CoolLayer's `display` is an all empty method, those `NSLog` won't be printed out and the `touchesBegan` won't print out `NSLog` messages, but if I add `[super display];` to that empty `display` method, then all those `NSLog` messages will be printed out – Jeremy L May 28 '12 at 21:07
  • Yes, this is as expected. See [Apple's docs](https://developer.apple.com/library/ios/#documentation/GraphicsImaging/Reference/CALayer_class/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004500) for info on the default behavior. – Svein Halvor Halvorsen May 28 '12 at 21:29
  • Oh which part in the doc is that? I mean, does the doc has a statement that says, if the `contents` is not touched or if `[super display]` is not called, then it will somehow hang or not show NSLog in other methods (such as in touchesBegan in UIView) – Jeremy L May 28 '12 at 21:39
  • Another update is that, if `[super display];` is removed, but a `self.contents = (id) [uiimage CGImage];` is added, then the image will be shown, and NSLog messages will be printed in FooView's `touchesBegan` method... but is touching or setting `contents` mandatory? The docs doesn't mention that it must be set.... – Jeremy L May 28 '12 at 21:41
  • Ok, I think I found the reason that NSLog messages in `touchesBegan` was not printed: there was nothing to touch. Or the size probably is like a (0,0), and there is nothing touched, and touchesBegan was never called. But I don't follow the logic that if `display` is empty, then `drawRect` is never called? I set an NSTimer to repeat every 1 second to `[self.view setNeedsDisplay];`, and `display` is called, but `drawRect` is not – Jeremy L May 28 '12 at 21:53
  • See the "discussion" paragraph of the `display` method. It states the default behavior (e.g. the behavior that you must either reimplement or otherwise replicate in a subclass), and also provides suggestions on how to implement it. – Svein Halvor Halvorsen May 28 '12 at 22:02
  • does it mention that drawRect is not called in some situation? I searched that doc and it doesn't mention drawRect at all... Also, I am suspecting `display` does this: it can either set `contents` to a CGImage object, or, it starts a ImageContext, and pass that ImageContext to drawInContext, and then when it returns, convert that ImageContext into a CGImage, and set it to `contents`... I hope some docs can verify this, and also, why is now drawRect not called even if an NSTimer sets the view with setNeedsDisplay? – Jeremy L May 28 '12 at 22:08
1

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.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • I think I found the reason why it was odd: no image was set and there was nothing to touch... but before I see your answer here, I post another question that probably describes how `display` and `drawInContext` works... but not sure how it won't call `drawRect`: http://stackoverflow.com/questions/10791025/on-ios-setneedsdisplay-really-doesnt-cause-drawrect-to-be-called-unless-cal . I think after I understand that, I have a pretty good understand of how CALayer caches the images, and how setNeedsDisplay really work and how `drawRect` is invoked – Jeremy L May 28 '12 at 22:29