Dynamic Layer Challenge


#1

OK - I’m stumped and I’m sure I should know this.

  1. the delegate of boxLayer cannot be HypnosisView.

Can’s figure out how to move up the hierarchy to set HypnosisViewController as the delegate.

I guess I need hint #4


#2

You could expose boxLayer as a property of HypnosisView or you could instantiate the boxLayer in the controller instead of within the implementation of HypnosisView.


#3

Thanks Joe, that helped a lot!


#4

Hi,
It seems that I stack in that challenge (Chapter 18). I am not able to get the delegate method

  • (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx

to be called. The method contains right now only a NSLog line…

I did exposed the boxLayer as a property of HypnosisView and then I made the HypnosisiViewController the delegate of boxLayer

[[hv boxLayer] setDelegate:self];

I understand that the delegate method will not be called unless the

[hv setNeedsDisplay];

in the HypnosisViewController has been called.
Are there any specific placed that the setDelegate and setNeedsDisplay should be called.

I will appreciate a small push forward.

/petron


#5

Hi,

have you tried calling setNeedsDisplay on the boxLayer itself from within HypnosisView ?

[boxLayer setNeedsDisplay]

Gareth


#6

Thanks a lot Gereth. It did work.
I am one step closer to the solution.
/petron


#7

Hi,

I’m fighting with the the dynamic layer challenge. After many different approaches, I did it and it works really good. Perfect!
But I’m confused about how does it works, or I think my ignorance is how this delegate works.

So, this is my final approach.

First, I’ve create a new Objective-C Class (UIView Subclass) file that I called ‘LayerDelegate’, with only one ivar,

CGRect screenSize; (with its property-synthesize pair)

and one method,

  • (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
    {
    UIImage *layerImage = [UIImage imageNamed:@“Hypno.png”];
    CGRect boundingBox = CGContextGetClipBoundingBox(ctx);
    CGContextSetAlpha(ctx, 1.0-[layer position].y/screenSize.size.width);
    CGContextDrawImage(ctx, boundingBox, [layerImage CGImage]);
    }

In HypnosisView.m I added the init method and I delegate it to boxLayer instance,

ld = [[LayerDelegate alloc] init];
[boxLayer setDelegate:ld];

I take the size of the main screen on the drawRect method,

[ld setScreenSize:[self bounds]];

and finally I added the setNeedsDisplay to the touchesBegan and touchesMoved methods.

I’ll be grateful if someone could clarify this concepts.

Thanks


#8

Hi,

I’ve been trying to understand how this delegation works.

At the beginning, I thought that the ‘drawLayer:’ method of my ‘LayerDelegate’ was continuously executed as it was in a different thread. In fact, I defined an instance of the ‘LayerDelegate’ as a delegate of the ‘boxLayer’.

When I touched the screen, the ‘touchesBegan:’ and ‘touchesMoved:’ methods of the HypnosisView.m were actualizing the ‘boxLayer’ status (with the variation of its alpha).

But, I made a little modification to see what happen.

I added to the ‘drawLayer’ method the following lines:

CGPoint p = CGPointMake([layer position].x+1.0, [layer position].y+1.0);
[layer setPosition:p];

And I deleted the lines,

    [boxLayer setPosition:p];

of the ‘touchesBegan:’ and ‘touchesMoved:’.

I wanted to see the boxLayer moving down to the bottom-right corner of the screen but what I saw was that it only moves when I touched the screen or when it was in the ‘touchesMoved:’ method.

And that it was not what I spected to see.

Can anyone explain me why this occur in this way?

Thanks in advance.


#9

@kanick: I don’t know if I understand your question correctly but the way I understand it, the drawLayer:inContext: method will only be triggered (once every time) when the message setNeedsDisplay is sent to boxLayer. You can try setting an NSTimer to send the setNeedsDisplay after a touch event.

All:

My solution was very similar to @kanick’s with the difference that I moved most of the code to the delegate method. Note that for brevity I hard coded the screen dimensions.

On HypnosisView.m add this code

    // Part of the challenge: set delegate
    BoxSlave *boxLayerDelegate = [[BoxSlave alloc] init];
    [boxLayer setDelegate:boxLayerDelegate];
    
    // Give it a size
    [boxLayer setBounds:CGRectMake(0.0, 0.0, 85.0, 85.0)];

    // Give it a location
    CGRect bounds = [self frame];
    
    // Where is its center?
    CGPoint center;
    center.x = bounds.origin.x + bounds.size.width / 2.0;
    center.y = bounds.origin.y + bounds.size.height / 2.0;
    [boxLayer setPosition:CGPointMake(160.0, 205.0)]; // center of the layer

    // Calling setNeedsDisplay 
    [boxLayer setNeedsDisplay];

BoxSlave.h

#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>

@interface BoxSlave : CALayer {
    
}

@end

BoxSlave.m

#import "BoxSlave.h"

@implementation BoxSlave
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
  NSLog(@"drawLayerInContext called");
  
  CALayer *boxLayer = layer;

  // Change layer transparency
  CGPoint p = [layer position];
  float a = 1.0 - p.y / 410.0; // TODO: Use a property to get the screen bounds 
                               // from it's superView
    
  // Make half-transparent red the background color for the layer
  UIColor *reddish = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:a];
  
  // Get a CGColor object with the same values
  CGColorRef cgReddish = [reddish CGColor];
  [boxLayer setBackgroundColor:cgReddish];
  
  // Create a UIImage
  UIImage *layerImage = [UIImage imageNamed:@"Hypno.png"];
  CGRect boundingBox = CGContextGetClipBoundingBox(ctx);

  // Get the underlying CGImage
  CGImageRef image = [layerImage CGImage];

  // Inset the image bit on each side
  [boxLayer setContentsRect:CGRectMake(-0.1,-0.1,1.2,1.2)];
  
  // Let the image resize (without changing the aspect ratio)
  // to fill the contentRect
  [boxLayer setContentsGravity:kCAGravityResizeAspect];
  

  // NOTE: Originally, this line came from the book
  // however it makes the layer redraw the image EVERY OTHER TIME !!!
  // this is not necessary as we are using CGContextDrawImage
  //[boxLayer setContents:(id)image]; // it's a CALayer so we use a CGImageRef
  
  //  CGContextClearRect(ctx, boundingBox);

  // Make transparent and redraw
  CGContextSetAlpha(ctx, a);
  CGContextDrawImage(ctx, boundingBox, image);

}
@end