Forcing Consecutive Animations with CABasicAnimation - objective-c

I have a notification that fires in my model when certain properties change. As a result, a selector in a particular view object catches the notifications to change the position of the view accordingly.
The notifications causes a view on the window to move in a particular direction (always vertically or horizontally and always by a standard step size on the window). It's possible for the user-actions to cause several notifications to fire one after the other. For example, 3 notifications could be sent to move the view down three steps, then two more notifications could be sent to move the view to the right two steps.
The problem is that as I execute the animations, they don't happen consecutively. So, in the previous example, though I want the view to move slowly down three spaces and then move over two spaces as a result of the notifications, instead it ends up moving diagonally to the new position.
Here is the code for my two selectors (note that placePlayer sets the position of the view according to current information in the model):
- (void)moveEventHandler: (NSNotification *) notification
{
[self placePlayer];
CABasicAnimation* moveAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
moveAnimation.duration = 3;
moveAnimation.fillMode = kCAFillModeForwards; // probably not necessary
moveAnimation.removedOnCompletion = NO; // probably not necessary
[[self layer] addAnimation:moveAnimation forKey:#"animatePosition"];
}
Any suggestions on how to make multiple calls to this methods force animation to execute step-by-step rather than all at once? Thanks!!

I think what you might want to do here is set up a queue of animations that need to happen consecutively and set the animations delegate so that you will receive the animationDidStop:finished: message. This way when one animation is completed you can set off the next one in the queue.

The solution I implemented does indeed use a queue. Here's a pretty complete description:
This is all done in a view class called PlayerView. In the header I include the following:
#import "NSMutableArray+QueueAdditions.h"
#interface PlayerView : UIImageView {
Player* representedPlayer; // The model object represented by the view
NSMutableArray* actionQueue; // An array used as a queue for the actions
bool animatingPlayer; // Notes if the player is in the middle of an animation
bool stoppingAnimation; // Notes if all animations should be stopped (e.g., for re-setting the game)
CGFloat actionDuration; // A convenient way for me to change the duration of all animations
// ... Removed other variables in the class (sound effects, etc) not needed for this example
}
// Notifications
+ (NSString*) AnimationsDidStopNotification;
#property (nonatomic, retain) Player* representedPlayer;
#property (nonatomic, retain, readonly) NSMutableArray* actionQueue;
#property (nonatomic, assign) CGFloat actionDuration;
#property (nonatomic, assign) bool animatingPlayer;
#property (nonatomic, assign) bool stoppingAnimation;
// ... Removed other properties in the class not need for this example
- (void)placePlayer; // puts view where needed (according to the model) without animation
- (void)moveEventHandler:(NSNotification *) notification; // handles events when the player moves
- (void)rotateEventHandler:(NSNotification *) notification; // handles events when the player rotates
// ... Removed other action-related event handles not needed for this example
// These methods actually perform the proper animations
- (void) doMoveAnimation:(CGRect) nextFrame;
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection;
// ... Removed other action-related methods not needed for this example
// Handles things when each animation stops
- (void) animationDidStop:(NSString*)animationID
finished:(BOOL)finished
context:(void*)context;
// Forces all animations to stop
- (void) stopAnimation;
#end
As an aside, the QueueAdditions category in NSMutableArray+QueueAdditions.h/m looks like this:
#interface NSMutableArray (QueueAdditions)
- (id)popObject;
- (void)pushObject:(id)obj;
#end
#implementation NSMutableArray (QueueAdditions)
- (id)popObject
{
// nil if [self count] == 0
id headObject = [self objectAtIndex:0];
if (headObject != nil) {
[[headObject retain] autorelease]; // so it isn't dealloc'ed on remove
[self removeObjectAtIndex:0];
}
return headObject;
}
- (void)pushObject:(id)obj
{
[self addObject: obj];
}
#end
Next, in the implementation of the PlayerView, I have the following:
#import "PlayerView.h"
#import <QuartzCore/QuartzCore.h>
#implementation PlayerView
#synthesize actionQueue;
#synthesize actionDuration;
#synthesize animatingPlayer;
#synthesize stoppingAnimation;
// ... Removed code not needed for this example (init to set up the view's image, sound effects, actionDuration, etc)
// Name the notification to send when animations stop
+ (NSString*) AnimationsDidStopNotification
{
return #"PlayerViewAnimationsDidStop";
}
// Getter for the representedPlayer property
- (Player*) representedPlayer
{
return representedPlayer;
}
// Setter for the representedPlayer property
- (void)setRepresentedPlayer:(Player *)repPlayer
{
if (representedPlayer != nil)
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[representedPlayer release];
}
if (repPlayer == nil)
{
representedPlayer = nil;
// ... Removed other code not needed in this example
}
else
{
representedPlayer = [repPlayer retain];
if (self.actionQueue == nil)
{
actionQueue = [[NSMutableArray alloc] init];
}
[actionQueue removeAllObjects];
animatingPlayer = NO;
stoppingAnimation = NO;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(moveEventHandler:)
name:[Player DidMoveNotification]
object:repPlayer ];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(rotateEventHandler:)
name:[Player DidRotateNotification]
object:repPlayer ];
// ... Removed other addObserver actions and code not needed in this example
}
}
// ... Removed code not needed for this example
- (void) placePlayer
{
// Example not helped by specific code... just places the player where the model says it should go without animation
}
// Handle the event noting that the player moved
- (void) moveEventHandler: (NSNotification *) notification
{
// Did not provide the getRectForPlayer:onMazeView code--not needed for the example. But this
// determines where the player should be in the model when this notification is captured
CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];
// If we are in the middle of an animation, put information for the next animation in a dictionary
// and add that dictionary to the action queue.
// If we're not in the middle of an animation, just do the animation
if (animatingPlayer)
{
NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithCGRect:nextFrame], #"nextFrame",
#"move", #"actionType",
#"player", #"actionTarget",
nil];
[actionQueue pushObject:actionInfo];
}
else
{
animatingPlayer = YES; // note that we are now doing an animation
[self doMoveAnimation:nextFrame];
}
}
// Handle the event noting that the player rotated
- (void) rotateEventHandler: (NSNotification *) notification
{
// User info in the notification notes the direction of the rotation in a RotateDirection enum
NSDictionary* userInfo = [notification userInfo];
NSNumber* rotateNumber = [userInfo valueForKey:#"rotateDirection"];
// Did not provide the getRectForPlayer:onMazeView code--not needed for the example. But this
// determines where the player should be in the model when this notification is captured
CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];
if (animatingPlayer)
{
NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithCGRect:nextFrame], #"nextFrame",
#"rotate", #"actionType",
rotateNumber, #"rotateDirectionNumber",
#"player", #"actionTarget",
nil];
[actionQueue pushObject:actionInfo];
}
else
{
enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
animatingPlayer = YES;
[self doRotateAnimation:nextFrame inDirection:direction];
}
}
// ... Removed other action event handlers not needed for this example
// Perform the actual animation for the move action
- (void) doMoveAnimation:(CGRect) nextFrame
{
[UIView beginAnimations:#"Move" context:NULL];
[UIView setAnimationDuration:actionDuration];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
self.frame = nextFrame;
[UIView commitAnimations];
}
// Perform the actual animation for the rotate action
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection
{
int iRot = +1;
if (rotateDirection == CounterClockwise)
{
iRot = -1;
}
[UIView beginAnimations:#"Rotate" context:NULL];
[UIView setAnimationDuration:(3*actionDuration)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
CGAffineTransform oldTransform = self.transform;
CGAffineTransform transform = CGAffineTransformRotate(oldTransform,(iRot*M_PI/2.0));
self.transform = transform;
self.frame = nextFrame;
[UIView commitAnimations];
}
- (void) animationDidStop:(NSString*)animationID
finished:(BOOL)finished
context:(void *)context
{
// If we're stopping animations, clear the queue, put the player where it needs to go
// and reset stoppingAnimations to NO and note that the player is not animating
if (self.stoppingAnimation)
{
[actionQueue removeAllObjects];
[self placePlayer];
self.stoppingAnimation = NO;
self.animatingPlayer = NO;
}
else if ([actionQueue count] > 0) // there is an action in the queue, execute it
{
NSDictionary* actionInfo = (NSDictionary*)[actionQueue popObject];
NSString* actionTarget = (NSString*)[actionInfo valueForKey:#"actionTarget"];
NSString* actionType = (NSString*)[actionInfo valueForKey:#"actionType"];
// For actions to the player...
if ([actionTarget isEqualToString:#"player"])
{
NSValue* rectValue = (NSValue*)[actionInfo valueForKey:#"nextFrame"];
CGRect nextFrame = [rectValue CGRectValue];
if ([actionType isEqualToString:#"move"])
{
[self doMoveAnimation:nextFrame];
}
else if ([actionType isEqualToString:#"rotate"])
{
NSNumber* rotateNumber = (NSNumber*)[actionInfo valueForKey:#"rotateDirectionNumber"];
enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
[self doRotateAnimation:nextFrame inDirection:direction];
}
// ... Removed code not needed for this example
}
else if ([actionTarget isEqualToString:#"cell"])
{
// ... Removed code not needed for this example
}
}
else // no more actions in the queue, mark the animation as done
{
animatingPlayer = NO;
[[NSNotificationCenter defaultCenter]
postNotificationName:[PlayerView AnimationsDidStopNotification]
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys: nil]];
}
}
// Make animations stop after current animation by setting stopAnimation = YES
- (void) stopAnimation
{
if (self.animatingPlayer)
{
self.stoppingAnimation = YES;
}
}
- (void)dealloc {
if (representedPlayer != nil)
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
[representedPlayer release];
[actionQueue release];
// …Removed other code not needed for example
[super dealloc];
}
#end
Explanation:
The view subscribes to appropriate notifications from the model object (player). When it captures a notification, it checks to see if it is already doing an animation (using the animatingPlayer property). If so, it takes the information from the notification (noting how the player is supposed to be animated), puts that information into a dictionary, and adds that dictionary to the animation queue. If there is no animation currently going, the method will set animatingPlayer to true and call an appropriate do[Whatever]Animation routine.
Each do[Whatever]Animation routine performs the proper animations, setting a setAnimationDidStopSelector to animationDidStop:finished:context:. When each animation finishes, the animationDidStop:finished:context: method (after checking whether all animations should be stopped immediately) will perform the next animation in the queue by pulling the next dictionary out of the queue and interpreting its data in order to call the appropriate do[Whatever]Animation method. If there are no animations in the queue, that routine sets animatingPlayer to NO and posts a notification so other objects can know when the player has appropriately stopped its current run of animations.
That's about it. There may be a simpler method (?) but this worked pretty well for me. Check out my Mazin app in the App Store if you're interested in seeing the actual results.
Thanks.

You should think about providing multiple points along the animation path in an array as shown below.
The example below specifies multiple points along the y-axis but you can also specify a bezier path that you want your animation to follow.
The main difference between basic animation and key-frame animation is that Key-frame allows you to specify multiple points along the path.
CAKeyframeAnimation *downMoveAnimation;
downMoveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.y"];
downMoveAnimation.duration = 12;
downMoveAnimation.repeatCount = 1;
downMoveAnimation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:20],
[NSNumber numberWithFloat:220],
[NSNumber numberWithFloat:290], nil];
downMoveAnimation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0], nil];
downMoveAnimation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
// from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil];
// from keyframe 2 to keyframe 3
downMoveAnimation.removedOnCompletion = NO;
downMoveAnimation.fillMode = kCAFillModeForwards;

Related

Touches are getting noticed twice in objective c

I am implementing session inactivity for my app so that if user is inactive for 30 seconds, then show him a new uiviewcontroller as a formsheet. For touch event, i am using this code
(void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
// Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
// allTouches count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded) {
[[BCDTimeManager sharedTimerInstance]resetIdleTimer];
}
}
}
In BCDTimeManager class which is a singleton class i have implemented resetIdleTimer and idleTimerExceed method
#import "BCDTimeManager.h"
#implementation BCDTimeManager
__strong static BCDTimeManager *sharedTimerInstance = nil;
NSTimer *idleTimer;
NSTimeInterval timeinterval;
+ (BCDTimeManager*)sharedTimerInstance
{
static dispatch_once_t predicate = 0;
dispatch_once(&predicate, ^{
sharedTimerInstance = [[self alloc] init];
NSString *timeout = [[NSUserDefaults standardUserDefaults] valueForKey:#"session_timeout_preference"];
timeinterval = [timeout doubleValue];
});
return sharedTimerInstance;
}
- (void)resetIdleTimer {
if (idleTimer) {
[idleTimer invalidate];
}
idleTimer = nil;
NSLog(#"timeout is %ld",(long)timeinterval);
idleTimer = [NSTimer scheduledTimerWithTimeInterval:timeinterval target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:true];
}
- (void)idleTimerExceeded {
NSLog(#"idle time exceeded");
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ApplicationTimeout" object:nil];
}
But when i do any touch on the screens, in console, i can see NSLog is printed twice which is causing my NSNOtification action to be triggered twice.
I am not sure what i am doing wrong. Please help me to figure out this.
I figured it out. Code is doing right. I am seeing NSLog twice because of two touch event one touch began and one touch ended. So, this code is correct without any issue. Something is wrong with observers add or remove method. I will look into that

Animation is not being performed through to another view controller

I am trying to trigger an animation in one view, based on what is happening in a separate class file. It gets to the method, supported by the fact that it does spit out the two NSLog statements, but doesn't commit the UIView Animation
Here is the code:
ViewController.h
#interface ViewController : UIViewController {
}
-(void)closeMenu;
ViewController.m
-(void)closeMenu{
//close the menu
NSLog(#"Got here");
[UIView animateWithDuration:0.6 animations:^{
[UIView setAnimationBeginsFromCurrentState:YES]; // so it doesn't cut randomly, begins from where it is
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
}];
NSLog(#"Got here2");
}
OtherClass.m (commented code may be irrelevant to this question, of course still used in actual application. Jut thought it might make it easier for comprehension)
#import "ViewController.h"
...
//- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
//{
ViewController *foo = [[[ViewController alloc] init] autorelease];
//SelectableCellState state = subItem.selectableCellState;
//NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
//switch (state) {
//case Checked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[foo closeMenu];
//break;
//case Unchecked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
//break;
//default:
//break;
//}
}
You're mixing old fashioned animation, with block based animation. For example, in the documentation, it states for setAnimationBeginsFromCurrentState:
Use of this method is discouraged in iOS 4.0 and later. Instead, you
should use theanimateWithDuration:delay:options:animations:completion:
method to specify your animations and the animation options.
I'm not 100% sure if this is supported. You should change your animation code to this at least:
[UIView animateWithDuration:0.6
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut
animations:^{
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
}
completion:nil];
Apart from that, it seems like it should work. It may cause issues if anything else if affecting the frame. It may be worth calculating the frame before the animation block. A'la:
CGRect newFrame = CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)
[UIView animateWithDuration...:^{ menuView.frame = newFrame; }...];
EDIT: Oh wait, looks like you're alloc/init'ing the object in (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem, and calling the method on it, but the view is nowhere in the view hierarchy. You need to call the animation on an instance which is being displayed on screen. Hope that makes sense?
EDIT 2: To call it on an instance which is already being displayed, typically you need to store it in an instance variable. I can't say exactly how in your situation, but generally it'd be of the form:
#interface OtherClass () {
ViewController* m_viewController;
}
#end
....
- (void)viewDidLoad // Or where ever you first create your view controller
{
...
// If you're adding a ViewController within another ViewController, you probably need View Controller Containment
m_viewController = [[ViewController alloc] init];
[self addChildViewController:m_viewController];
[m_viewController didMoveToParentViewController:self];
[self.view addSubview:m_viewController.view];
...
}
// If you're using ARC code, the dealloc wouldn't typically be necessary
- (void)dealloc
{
[m_viewController release];
[super dealloc];
}
//- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
//{
//SelectableCellState state = subItem.selectableCellState;
//NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
//switch (state) {
//case Checked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[m_viewController closeMenu];
//break;
//case Unchecked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
//break;
//default:
//break;
//}
}
If you need to access it from outside the class, this won't be sufficient, use properties for that. I.e.
Header File
#property (nonatomic, strong) ViewController* myViewController
.m file
// Use it as such
[self.myViewController closeMenu];
That animation code is really strange. You are mixing the new and the old UIView animation code and I don't think you can do that (but I could be wrong).
Since your have begun using the block based API I would recommend going that route (Apple recommends the same thing).
There is a similar method to the one you've used that takes options called animateWithDuration:delay:options:animations:completion:. You can pass in 0 for the delay and an empty block that takes a BOOL for the completion.
The two flags you want to pass for the options are UIViewAnimationOptionBeginFromCurrentState and UIViewAnimationOptionCurveEaseInOut.
Your code would look something like this
[UIView animateWithDuration:0.6
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
animations:^{
// set your frame here...
[menuView setFrame:CGRectMake(menuView.frame.origin.x,
-menuView.frame.size.height,
menuView.frame.size.width,
menuView.frame.size.height)];
} completion:^(BOOL finished){}];

CATransition also animating the navigationBar

I want to make a custom animation to pop my navigation controller. I only want to animate the view, not the navigationBar. With this code I animate both, the view and the navigationBar. How can I only animate the view??
CATransition* transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];
this code is fired when a custom back button added inside the navigationcontroller bar is pressed.
Here is a code that does custom animation both for back button and when you call popRootViewController: method.
It's a class that extends UINavigationViewController which by itself contradicts Apple's docs also it assigns private variable using KVO, which might stop working as soon as engineers change UINavigationController class so use it a t your own risk.
#import "MyNavigationController.h"
#interface MyNavigationController () <UINavigationBarDelegate> {
// Flag that we will use to avoid collisions between navgiation bar
// when we call popViewControllerAnimated: method directly
BOOL _isPopping;
}
- (UIViewController *)myPopViewControllerAniamted:(BOOL)animated;
#end
#implementation MyNavigationController
- (id)init
{
self = [super init];
if (!self) return nil;
// We can't intercept delegation of the original navigation bar,
// we have to replace it with our own, by assigning new instance to
// the private _navigationBar vairable
UINavigationBar *navigationBar = [[UINavigationBar alloc] init];
navigationBar.delegate = self;
[self setValue:navigationBar forKey:#"_navigationBar"];
return self;
}
// This is the delegate method called when you're about to pop navigation item
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
// If we're in the process of popping items we don't want to reenter
if (!_isPopping) {
[self myPopViewControllerAniamted:YES];
}
return YES;
}
// Similarly we have to override popToRootViewControllerAnimated:
// The only difference would be that we use not previous view as a
// target for the transfition, but the very first view
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
return [self myPopViewControllerAniamted:animated];
}
// Our custom popping method
- (UIViewController *)myPopViewControllerAniamted:(BOOL)animated
{
_isPopping = YES;
// If we got here, we have at least two view controllers in the stack
UIViewController *currentViewController = self.topViewController;
if (animated && self.viewControllers.count > 1) {
UIView *currentView = currentViewController.view;
UIViewController *previousViewController = [self.viewControllers objectAtIndex:self.viewControllers.count - 2];
UIView *previousView = previousViewController.view;
previousView.alpha = 0.0;
[currentView.superview insertSubview:previousView belowSubview:currentView];
// I use UIView just for the sake of the simplicity of this example
// In case of core animation you will have to deal with delegates
// to trigger view controller popping when animation finishes
[UIView animateWithDuration:0.33 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
currentView.alpha = 0.0;
previousView.alpha = 1.0;
} completion:^(BOOL finished) {
[super popViewControllerAnimated:NO];
_isPopping = NO;
}];
} else {
[super popViewControllerAnimated:NO];
_isPopping = NO;
}
return currentViewController;
}
#end
Once again, it was done purely as exercise of what is possible, I would highly recommend reading UIViewController guide, probably Container View Controller can satisfy you needs as a designated way of customizing view controller behaviour.
Hope it helps!

Objective - C: `self` not responding

I have a cocos2d layer named ChoosePlayer and in init method I am adding a few sprite using [self addChild:]. Its plain and works right. But when I try to do the same in another method as given below, its not working:
-(void) avatarchanged {
[self addChild:[CCSprite spriteWithFile:#"av1.png"]];
[self runAction:[CCMoveBy actionWithDuration:1.0 position:ccp(100, 100)]];
NSLog(#"added new avatar");
}
The [self runAction:] is also not responding. So I guess its not the problem with sprite, but with the self itself.
In between the init and avatarchanged, what I am doing is showing a UIView on top of openGL View, perform some actions there and returning back as follows:
-(void) selectAvatar {
CGSize winSize = [CCDirector sharedDirector].winSize;
flowCoverView = [[[FlowCoverView alloc] initWithFrame: CGRectMake(0, 0, 480, 320)] autorelease];
flowCoverView.center = ccp(-80 + winSize.width / 2, 80 + winSize.height / 2);
flowCoverView.delegate = self;
flowCoverView.transform = CGAffineTransformMakeRotation(90*(3.14/180));
[[CCDirector sharedDirector].openGLView.window addSubview:flowCoverView];
}
When the necessary actions are performed, flowCoverView is removed as follows:
- (void)flowCover:(FlowCoverView *)view didSelect:(int)cover {
selectedavat = cover;
[flowCoverView removeFromSuperview];
[[NSNotificationCenter defaultCenter] postNotificationName:#"avatarchanged" object:nil];
}
The notification posted above invoked my avatarchanged method, where the self is not responding.
Edit: here is my init method:
-(id) init {
if( (self=[super init])) {
self.isTouchEnabled = YES;
BG = [CCSprite spriteWithFile:#"opponent.jpg"];
BG.scale *= CC_CONTENT_SCALE_FACTOR() * 1;
BG.position = ccp(240,160);
[self addChild:BG];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(avatarchanged) name:#"avatarchanged" object:nil];
}
return self;
}
Note: In my project there is a bunch of global variables declared using extern, they might something to do with my problem, but am not sure.
Could someone please help me with this?
Edit 2:
changed avatarchanged as follows:
-(void) avatarchanged {
if (self == nil) {
NSLog(#"self is nil!!!!!!!!");
} else {
NSLog(#"pheww.. its not nil");
}
if (self.isRunning) {
NSLog(#"running");
} else {
NSLog(#"not running");
}
[BG runAction:[CCRotateBy actionWithDuration:1.0 angle:100.0]];
[self addChild:[CCSprite spriteWithFile:#"av1.png"]];
NSLog(#"added new avatar");
[self runAction:[CCMoveBy actionWithDuration:1.0 position:ccp(100, 100)]];
}
log shows as
2012-03-26 11:16:21.213 Funsip[1550:207] pheww.. its not nil
2012-03-26 11:16:21.214 Funsip[1550:207] running
2012-03-26 11:16:21.224 Funsip[1550:207] added new avatar
the BG's runAction is also not getting applied, but doing the same in init method works perfectly right.
Edit 3:
The FlowCoverView that I add is implemented with OpenGL calls internally. May be it could be causing conflicts with OpenGL states setup in cocos2d. But I dont know OpenGL to look for these sort of issues.
Here is the link to the page where I took the flowcoverview from http://www.chaosinmotion.com/flowcover.html
Is 'self' in running mode (self.isRunning) ? if not nothing much will happen from a cocos2d point of view. The isRunning mode is achieved when you add the ChoosePlayer instance to a running CCNode descendant. If you forgot to add it to a running node, it will be ignored in draws, actions, etc ...
This might help: http://www.cocos2d-iphone.org/forum/topic/28056

Keyboard Events Objective C

I'm having trouble receiving keyboard events within a subclass of NSView.
I can handle mouse events fine, but my keyDown and keyUp methods are never called. It was my understanding per the documentation that both types of events follow the same hierarchy, however this is seemingly not the case.
Is this a first responder issue? Some field somewhere grabbing the focus? I've tried overriding that but no luck.
Any insights would be greatly appreciated.
If you'd like to see.. this is within a custom NSView class:
#pragma mark -
#pragma mark I/O Events
-(void)keyDown:(NSEvent *)theEvent {
NSLog(#"Sup brah!");
}
-(void)keyUp:(NSEvent *)theEvent {
NSLog(#"HERE");
}
// This function works great:
-(void)mouseDown:(NSEvent *)theEvent {
NSNumber *yeah = [[NSNumber alloc] initWithBool:YES];
NSNumber *nah = [[NSNumber alloc] initWithBool:NO];
NSString *asf = [[NSString alloc] initWithFormat:#"%#", [qcView valueForOutputKey:#"Food_Out"]];
if ([asf isEqualToString:#"1"]) {
[qcView setValue:nah forInputKey:#"Food_In"];
best_food_x_loc = convertToQCX([[qcView valueForOutputKey:#"Food_1_X"] floatValue]);
best_food_y_loc = convertToQCY([[qcView valueForOutputKey:#"Food_1_Y"] floatValue]);
NSLog(#"X:%f, Y:%f",best_food_x_loc, best_food_y_loc);
} else {
[qcView setValue:yeah forInputKey:#"Food_In"];
}
}
You have to set your NSView to be first responder
- (BOOL)acceptsFirstResponder {
return YES;
}