drawLayer Method - iphone

I have drawLayer method, which i'm calling from another method using setNeedsDisplay method, but the flow of code seems to be incorrect.
I have this method
-(IBAction)makeSinglePageMode:(UIButton *)sender
from where i'm calling another method
-(void)loadSinglePageWithWidth:(float)width andHeight:(float)height
which in turn calls `drawLayer` method.
I'm attaching the methods.I'm attaching only a part of code in these methods just to have more clarity while understanding the flow.
After calling [tiledLayer1 setNeedsDisplay]; the flow comes back to makeSinglePageMode: method without executing drawLayer method.
The next statement in makeSinglePageMode: method gets executed i.e. [self loadMarkUpFromPageDataSource:pd WithPageSize:pageSize inFrame:markupFrame];
I want this line of code to be executed only after drawLayer method execution is completed.
Any sort of help in this regard will be appreciated.
`
-(IBAction)makeSinglePageMode:(UIButton *)sender{
GLogDebug(#"setting size of image to %#", NSStringFromCGSize(aspectFitSize));
[scrollView setContentSize:CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height - __TOP_BRANDING_BAR_HEIGHT)];
[markupView removeAllSubViews];
tiledLayer1.frame=CGRectMake(0,0,768,1024);
[self loadSinglePageWithWidth:(aspectFitSize.width) andHeight:aspectFitSize.height];
GLogDebug(#"markupFrame-%#", NSStringFromCGRect(markupFrame));
[self loadMarkUpFromPageDataSource:pd WithPageSize:pageSize inFrame:markupFrame];
}`
-(void)loadSinglePageWithWidth:(float)width andHeight:(float)height{
[tiledLayer1 setNeedsDisplay];
}
`- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
int c=[currentPage intValue]+1;
if(c==[kbDataSource numberOfPagesInBook]){
tiledLayer2.hidden=YES;
}
else tiledLayer2.hidden=NO;
}
CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);
CGContextDrawPDFPage(ctx, myPageRef);
CGContextRestoreGState(ctx);
}
}

Related

animationDidStop for group animation

I have a group animation but I can not detect when hits animationDidStop. example of my code:
[group setDelegate:self];
[view.layer addAnimation:group forKey:#"groupAnimation"];
any of you knows how I know when the group animation is done?
You need to also set the animationName property to match, and ensure that your delegate function is properly defined:
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 2.0f;
group.delegate = self;
[group setValue:#"groupAnimation" forKey:#"animationName"];
[group setAnimations:[NSArray arrayWithObjects:myAnimation, myOtherAnimation, nil]];
[view.layer addAnimation:group forKey:#"groupAnimation"];
.
.
.
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
if (finished)
{
NSString *animationName = [animation valueForKey:#"animationName"];
if ([animationName isEqualToString:#"groupAnimation"])
{
// your groupAnimation has ended
}
}
}
Please note that with group animations, the delegates set on your component animations will be ignored.
I came up with a way to organize animation completion code that works really well. First, I defined a custom type for a block of code to run when an animation completes:
typedef void (^animationCompletionBlock)(void);
I define a constant that I use to add a custom key-value pair to my animation:
#define kAnimationCompletionBlock #"animationCompletionBlock"
Then, when I create an animation, if I want it to execute a block of code when it finishes, I add a custom property to my animation that contains the block of code I want to execute:
animationCompletionBlock theBlock;
theBlock = ^void(void)
{
someButton.enabled = TRUE;
NSLog(#"Animation complete");
//whatever other code you want to do on completion
}
[myAnimation setValue: theBlock forKey: kAnimationCompletionBlock];
I set the view controller to be the animation's delegate:
myAnimation.delegate = self
Finally, I write a general-purpose animation completion method that looks for an animation completion block attached to the animation, and executes it if it finds it:
/*
This method looks for a value added to the animation that just completed
with the key kAnimationCompletionBlock.
If it exists, it assumes it is a code block of type animationCompletionBlock,
and executes the code block.
This allows you to add a custom block of completion code to any animation or
animation group, rather than Having a big complicated switch statement in your
animationDidStop:finished: method with global animation ompletion code.
(Note that the system won't call the animationDidStop:finished method for
individual animations in an animation group - it will only call the
completion method for the entire group. Thus, if you want to run code after
part of an animation group completes, you have to set up a manual timer.)
*/
- (void)animationDidStop:(CAAnimation *)theAnimation
finished:(BOOL)flag
{
animationCompletionBlock theBlock =
[theAnimation valueForKey: kAnimationCompletionBlock];
if (theBlock)
theBlock();
}
In addition to being very clean, this approach also lets your animation completion code have access to local variables that are inside the scope where you define the block. That solves the problem of passing information to your completion method, which can be difficult.
You can see this technique in a working example program I posted to Github:
Core Animation demo on Github, including completion block code
I'm using this category for setting the completion like this:
[group setCompletionBlock:^{
}];
First CAAnimationGroup+Blocks.h:
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>
typedef void (^TIFAnimationGroupCompletionBlock)();
#interface CAAnimationGroup (Blocks)
- (void)setCompletionBlock:(TIFAnimationGroupCompletionBlock)handler;
#end
And CAAnimationGroup+Blocks.m:
#import "CAAnimationGroup+Blocks.h"
static char CAAnimationGroupBlockKey;
#implementation CAAnimationGroup (Blocks)
- (void)setCompletionBlock:(TIFAnimationGroupCompletionBlock)handler {
objc_setAssociatedObject(self, &CAAnimationGroupBlockKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
self.delegate = self;
}
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
if (finished)
{
TIFAnimationGroupCompletionBlock handler = (TIFAnimationGroupCompletionBlock)objc_getAssociatedObject(self, &CAAnimationGroupBlockKey);
if (handler) {
handler();
}
}
}
#end

CALayer on CATiledLayer, drawLayer:inContext never called

i have a UIView which display a PDF page using CATiledLayer, now i want to add another CALayer on TiledLayer to draw some annots, Please see the code below.
+ (Class)layerClass
{
return [TiledLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
TiledLayer *tiledLayer = (id)self.layer;
tiledLayer.backgroundColor = [UIColor whiteColor].CGColor;
NSAssert([tiledLayer isKindOfClass:[TiledLayer class]], #"self.layer must be CATiledLayer");
drawingLayer = [CALayer layer];
drawingLayer.frame = frame;
//its crashing if set the delegate, if not drawLayer is never called.
[drawingLayer setDelegate:self];
[self.layer addSublayer:self.drawingLayer];
}
}
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
if(layer == [self layer])
{
[self drawPDFPage];
[drawingLayer setNeedsDisplay];
}
else if(layer == drawingLayer)
{
//this one is never called.
[self drawSomethinghere];
}
}
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.
Implementing an empty -drawRect: method allows UIKit to continue to implement this logic, while doing the real drawing work inside of -drawLayer:inContext:.
Just add to you class implementation:
-(void)drawRect:(CGRect)r
{
}
And your drawLayer method should work.

CGContextDrawImage without Transaction animation

How do I draw a layer without a transaction animation? For example, when I set the contents of a layer using CATransaction it works well:
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
myLayer.contents = (id)[[imagesTop objectAtIndex:Number]CGImage];
[CATransaction commit];
but I need to change contents from the delegate method [myLayer setNeedsDisplay]. Here is the code:
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
CGContextDrawImage(context, rect, Image.CGImage]);
}
CALayer implements setNeedsDisplay method:
Calling this method will cause the receiver to recache its content. This will result in the layer receiving a drawInContext: which may result in the delegate receiving either a displayLayer: or drawLayer:inContext: message.
... and displayIfNeeded:
When this message is received the layer will invoke display if it has been marked as requiring display.
If you wrap [myLayer displayIfNeeded] in a CATransaction, you can turn off implicit animation.
You can subclass CALayer and override actionForKey:,
- (id <CAAction>) actionForKey: (NSString *) key
{
if ([key isEqualToString: #"contents"])
return nil;
return [super actionForKey: key];
}
This code disables the built-in animation for the contents property.
Alternatively, you can achieve the same effect by implementing the layer's delegate method
- (id <CAAction>) actionForLayer: (CALayer *) layer forKey: (NSString *) key
{
if (layer == myLayer) {
if ([key isEqualToString: #"contents"])
return nil;
return [[layer class] defaultActionForKey: key];
}
return [layer actionForKey: key];
}

Can I pass a function name as an argument ?

I want to make this class dynamically perform functions on itself by passing different names to it. Is that possible ? Or rather: How is it possible ?
-(id)initWithMethod:(NSString*)method{
if ((self = [super init])){
[self method];
}
return self;
}
-(void) lowHealth {
CCSprite *blackScreen = [CCSprite spriteWithFile:#"blackscreen.png"];
blackScreen.anchorPoint = ccp(0,0);
[self addChild:blackScreen];
id fadeIn = [CCFadeIn actionWithDuration:1];
id fadeOut = [CCFadeOut actionWithDuration:1];
id fadeInAndOut = [CCRepeatForever actionWithAction:[CCSequence actions:fadeIn, fadeOut, nil]];
[blackScreen runAction:fadeInAndOut];
}
You should use performSelector and get the selector from your NSString using NSSelectorFromString:
[self performSelector:NSSelectorFromString(method)];
instead of [self method];
The standard way is using Selectors as mentioned in Matteo's answer.
You can also look at Objective-C Blocks. They are becoming very common in the CocoaTouch APIs and you can do some very slick things with them. The resulting architecture of your class is often easier to understand IMO.
For example this method from UIView
+ (void)animateWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
Takes two block, one that is runs the code for the actual animation, and one to for the code after the animation is complete. You could call this with block variables or by writing code inline:
...animations:^{
// animation code
}
completion:^(BOOL finished) {
// completion code
}
The receiving method (in this case animateWithDuration:...) would simply call these blocks at some point like so:
animations();

Triggering code when UIPickerView selectRow:inComponent:animated:YES is completed?

My action method:
- (IBAction)buttonPressed {
// animate picker to random row (picker is a UIPickerView)
int row = random() % [self.column1 count];
[picker selectRow:row inComponent:0 animated:YES];
[picker reloadComponent:0];
// display new selected row content
int selectedRow = [picker selectedRowInComponent:0];
NSString *selectedItem = [self.column1 objectAtIndex:selectedRow];
myLabel.text = selectedItem; // UILabel under the picker
}
However
myLabel.text = selectedItem;
gets called before the animation has completed and so it doesn't display the new value.
I found the thread How to get callback from UIPickerView when the selectRow animation is done?, but the answer uses a beginAnimations/commitAnimation block - about which apple says: "Use of this method is discouraged in iOS 4.0 and later. You should use the block-based animation methods to specify your animations instead."
How would I use block-based animation to accomplish this?
It looks like Apple needs to update their UIPickerView API to support a block based completion handler. Until then, just wait the estimated time of the animation.
[picker selectRow:0 inComponent:0 animated:YES];
[self performSelector:#selector(setMyLabel) // setMyLabel - my function
withObject:nil
afterDelay:0.4];
Have you tried something like this:
[UIView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
// the animation code
[myPickerView selectRow:0 inComponent:0 animated:YES];
}
completion:^(BOOL finished) {
[self performSelector:#selector(setMyLabel)];
}
];
Or you could just set the label directly in the completion block, if that's the only thing you want to do after the animation completes.
EDIT:
The code above is the same as the code in the other thread you've mentioned, but using animation blocks instead. I actually didn't know if the method selectRow:inComponent:animated: will run on the same animation block thread or it will have its own thread. Since it didn't work, that means it runs on its own thread so the code I wrote won't work.
There is one way around to solve this, which is by blocking the main thread for the time needed for the animation to complete. You can call [NSThread sleepForTimeInterval:1.0] after your call to selectRow:inComponent:animated:, and you can adjust the interval until you reach a suitable value. Note that blocking the main thread is highly discouraged by Apple. Personally I would block it only if I couldn't think of another way to achieve what I'm trying to do and if the blocking time is smaller than 1.