completedAnimationSequenceNamed being called repeatedly - cocos2d-iphone

I'm using cocos2d and cocosBuilder.
I'm trying to do a starting animation, and then once it has finished, make all of the children nodes perform their idle animations.
how I'm doing the starting animation:
(void) onEnter
{
[super onEnter];
CCLOG(#"onEnter being called");
// Schedule a selector that is called every frame
[self schedule:#selector(update:)];
// Make sure touches are enabled
self.touchEnabled = YES;
//intro animation
CCBAnimationManager* animationManager = self.userObject;
animationManager.delegate = self;
[animationManager runAnimationsForSequenceNamed:#"words"];
}
and this is what my completedAnimationSequenceNamed looks like:
(void) completedAnimationSequenceNamed:(NSString *)name{
CCLOG(#"animation ended");
for(CCNode *c in [self children])
{
if([c isKindOfClass:[GameObject class]])
[c playIdleAnimation];
}
}
For some reason, my completedAnimationSequenceNamed is being called repeatedly, many times a second, causing my children to constantly restart their idle animations.
Any ideas?

I got it working, this is for anybody else running into this problem.
In order for the "(void) completedAnimationSequenceNamed:(NSString *)name" method to work properly you must have your animation start automatically (in the cocosBuilder project) instead of using running Animations from animationManager.
If I used the runAnimation method from animationManager, it (essentially) broke the callback method. But I just took that out and made the animation run automatically and it calledback as expected.

Related

interval update debug of uknown interval

I've got the basic engine of a game put together rather nicely, my issue at the moment is that while running it from my computer onto my iPhone, I'm getting a strange debug message:
CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: 0.0000 to 0.0000
This debug message is being displayed every frame or nearly every frame
Now, I don't have any scheduled updates that are on interval 0, so this makes me think that it must be the generic scheduleUpdate command. I did a search entire project to see if any class had multiple scheduleUpdates and none do so now I am confused. Any thoughts?
I generally get this message when I schedule something that's already scheduled. See if there's anything like this in your code:
[self schedule:#selector(gameLogic:) interval:1.0];
- (void) gameLogic : (ccTime)dt
{
[self doLogic];
[self schedule:#selector(updateTimers:) interval:1.0];
[self schedule:#selector(gameLogic:) interval:1.0];
}
By repeatedly scheduling something inside a timed event, you'll get a warning that you've called the timer a second time.

How do you detect what UIPickerView row has been selected using an if statement

I have made a button so that when it's pressed by the user and a particular row(s) are selected it does something.
So far I have this:
if (pickerView selectedRowInComponent:0) {
[mailComposerTwo setToRecipients:[NSArray arrayWithObjects:#"email#blah.com",nil]];
}
It works on its own. But when I do the if statement multiple times it crashes.
An ways of making it work?
Any help appreciated, thanx.
The problem probably lies with your mail composer, not the picker view. When you show the composer, make sure that you only create it if it hasn't already created.
Also, make sure you release it after you show it:
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
...[configure the picker]
[rootContainer presentModalViewController:picker animated:YES];
[picker release];
NSArray *finalList = [[NSArray alloc]init];
//put all your if statements
if (pickerView selectedRowInComponent:0)
{
[finalList arrayByAddingObjectsFromArray:#[#"email#address.com",#"second#address.com",...];
}
if (pickerView selectedRowInComponent:1)
{
[finalList arrayByAddingObjectsFromArray:#[#"another#address.com",#"fourth#address.com",...];
}
//end of if statements
[mailComposerTwo setToRecipients:finalList];
[self presentViewController:yourInitializedMessageController animated:YES completion:^{NSLog(#"message controller is presented");}];
This will do a single method call rather than continually reassigning which for some odd reason is causing your exception. presentModalViewController:animated: has been deprecated as of iOS 6.0? if not 7.0 I believe.
NOTE! Make the message controller a property of the main view controller. It is good practice so that it is not auto-released by iOS if you need to bring it back up. However if you use MFMessageComposer iOS will keep messenger allocated or running in a thread somewhere so initializing a view controller for it is quick.

NSOpenPanel from C++ boost thread

I'm invoking a NSOpenPanel from a thread created by boost C++.
the panel behaves erratically and doesn't respond well to mouse, that is clicking on objects does nothing sometime when clicking on top level combo box does improve response.
do i've to run a separate runloop I'm doing a runModalForDirectory which should take care of running its own loop.
I've also created a separate objc class which does performSelectorOnMainThread to show panel in main thread but still the behavior is same.
[ps performSelectorOnMainThread:#selector(showOpenPanel) withObject:nil
waitUntilDone:YES
modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
I've also tried with waitUntilDone:NO and running a CFRunLoopRunInMode which isn't helping either.
- (bool) showOpenPanel
{
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setAllowsMultipleSelection:YES];
[op setTitle:#"Choose File"];
[op setMessage:#"Choose file for Importing."];
[op setFloatingPanel:true];
bool result =[op runModalForDirectory:NSHomeDirectory() file:nil types:self.fileTypes];
if (result == NSOKButton) {
[self setSelectedFiles:[op filenames]];
[self setLastShowResult:true];
}
else {
[self setLastShowResult:false];
}
[self setPanelIsDone:true];
return self.lastShowResult;
}
NSOpenPanel is part of AppKit. AppKit functions and classes can only be safely used on the main thread.
Show us the code you used with performSelectorOnMainThread so we can help figure out why you might still be seeing problems. I suspect you're calling individual methods with it--don't; it won't work the way you expect. Call back to the main thread for the totality of your interaction with NSOpenPanel.

How to check if CCAction is running - cocos2d

I'm using Cocos2d to write game for iPhone.
Here's the problem.
I have CCSprite and CCAction which is run on it.
CCSprite texture;
CCAction anim_action;
"Anim_action" is a CCRepeatForever action.
Now I want to check if this animation is running.
First I though I can use [isDone] function, but I think it doesn't work on CCRepatForever actions (I'm not sure - this opion is based on my tests)
So how to check if this animation is already running on my "texture"?
Maybe there is a way to get name of action which is running on texture at the moment?
It may be also useful!
There is a way to check if a specific action runs on your texture. Use:
CCAction *action = [texture getActionByTag:kAsignedActionTag];
where kAsignedActionTag is the tag assigned to the your animation.
anim_action.tag = kAsignedActionTag;
If your action is still running the getActionByTag method will not return nil.
I don't believe there's a way to directly tell if a CCRepeatForever action has completed since the isDone would make no sense, but there are some techniques you can use to essentially provide a callback to indicate if something is still running:
Override the step: method and call out to something that checks the interval - when it exceeds a threshold you can assume completion...kinda'
Wrap the inner action of the CCRepeatForever with a CCSequence. The first action of the sequence would be your repeated action and the second would be a CCCalFunc, again indicating that the action is still running
Subclass the CCRepeatForever and override the dealloc so you can fire a callback when the action is killed and released
You can easily use [isDone] while appling an effect
- (void)shakeThatThingOn: (BOOL)on { //you can return BOOL and get if the animation is working or not
if (on == YES){
id shaky2 = [CCShaky3D actionWithRange:3 shakeZ:NO grid:ccg(15,10) duration:5];
if (![shaky2 isDone])
[self runAction:[CCSequence actions:shaky2,[CCStopGrid action],nil]];
}
else {//this else is being called when you turn off animation (it's just 0.2s continuation after turning off - for better visual effect.
[self stopAllActions];
id shaky2 = [CCShaky3D actionWithRange:3 shakeZ:NO grid:ccg(15,10) duration:0.2];
[self runAction:[CCSequence actions:shaky2,[CCStopGrid action],nil]];
}}
and control it by simple BOOL if it's on or off.
I don't know if it's what you mean, but hope it'll help anyway.
If you know how many actions will be running on the sprite, or if the animation is the only action, then you can infer that the animation is running by checking the sprite's total number of running actions.
if ([texture numberOfRunningActions] > 0) //animation is running
or
if ([texture numberOfRunningActions] > someNumber) //if you had other actions running

WebView not showing in NSWindow

I am developing a C++ app and I need to display a NSWindow with a WebKit WebView inside it. I've coded up the Objective-C class which will manage creating and displaying the window but the WebView contained inside it does not display. Here is my code. Any idea on what is wrong and how I can fix it?
I'm compiling the below code with
$g++ -x objective-c++ -framework Cocoa -framework WebKit Foo.m main.m -o test
Foo.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#interface Foo :NSObject {
NSWindow *window;
WebView *view;
}
- (void)displayWindow;
#end
Foo.m
#import "Foo.h"
#implementation Foo
- (id)init {
self = [super init];
// Window Container
window = [[NSWindow alloc] initWithContentRect:NSMakeRect(500.0f,500.0f,250.0f,250.0f)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreNonretained
defer:NO];
// WebView
view = [[WebView alloc] initWithFrame:NSMakeRect(0, 0, 250.0f, 250.0f)
frameName:#"Frame"
groupName:nil];
[[view mainFrame] loadHTMLString:#"<html><head></head><body><h1>Hello</h1></body></html>"
baseURL:nil];
return self;
}
- (void)displayWindow {
NSLog(#"In Display window");
[window setContentView:view];
[window setLevel:NSStatusWindowLevel];
[window orderFrontRegardless];
sleep(5); // leave it up for 5 seconds
}
- (void)dealloc {
[window release];
[super dealloc];
}
#end
main.m
#import "Foo.h"
int main() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
Foo *foo = [[Foo alloc] init];
[foo displayWindow];
[foo release];
[pool release];
return 0;
}
You need to run the run loop. If you just order the window in and then exit, that's exactly what will happen: The window will appear, and then (five seconds later) your program will exit. You can run the run loop by telling the application (which you create but don't otherwise use) to run.
On the main thread of a Cocoa app, sleep is always the wrong answer. The same goes for its Cocoa cousins, +[NSThread sleepUntilDate:] and +[NSThread sleepForTimeInterval:]. The run loop will let you tell it to run for a fixed amount of time, but that won't get the application running; you do need to send the application the run message, which provides no opportunity to exit after a fixed interval.
The solution there is to first create an NSTimer object whose target is the application and whose selector is #selector(terminate:). Create it scheduled and non-repeating, with the interval set to five seconds. (Creating it scheduled means you don't need to schedule it separately—it is already ready to go from the moment you create it.) Then, send the application the run message. Five seconds later, the run loop will fire the timer, which will tell the application to terminate itself. This is assuming that you actually have a good reason to make your application quit after five seconds.
As noted by Yuji, every window in modern Cocoa should use NSBackingStoreBuffered.
And don't forget to release what you have created; you currently are forgetting that in the case of the view. See the Memory Management Programming Guide for Cocoa.
Once you have this working, I suggest moving toward a more typical architecture for this application:
Create a subclass of NSObject, and make an instance of that class your application's delegate.
Put the window and its WebView into a nib, and have the app delegate create a window controller to load and own the contents of that nib.
The app delegate should also be responsible for loading the page into the WebView and for setting up the self-termination timer.
Finally, create a nib to hold your application's main menu (the contents of the menu bar) and the application delegate. Interface Builder has a template for the first part; you create the app delegate object by dragging a blank Object in from the Library, setting its class on the ⌘6 Inspector, and dragging the connection from the application to the object. Then, you can reduce main to the single line that Xcode's project templates put in it: return NSApplicationMain(argc, argv);.
Doing all this will help your understanding of Cocoa, as well as your maintenance of the application—cramming everything into main will not scale.
You should also read the Cocoa Fundamentals Guide, if you haven't already.
Don't make it sleep. It stops the execution of the main thread, in which the GUI is dealt with. Instead, you need to run the run loop. Also, Cocoa needs to set itself up. So, call [[NSApplication sharedApplication] run] to set it up correctly and run the event loop.
Also, don't use backing mode other than buffered mode. Other modes are remnants from the time immemorial, and only NSBackingStoreBuffered should be used. As discussed in this Apple document, the non-retained mode is a remnant to support Classic Blue Box (OS 9 virtualizer), and newer classes like WebKit just can't operate within it.
So, what you need to do is practically:
change NSBackingStoreNonretained to NSBackingStoreBuffered.
Remove the line
sleep(5);
add a line
[[NSApplication sharedApplication] run];
after
[foo displayWindow];
Also, in order for an app to receive events from the window server correctly, you need to pack it into an app bundle. Compile it into a binary called foo, and create the following structure:
foo.app/
foo.app/Contents/
foo.app/Contents/MacOS/
foo.app/Contents/MacOS/foo <--- this is the executable
Then you can double-click foo.app from the Finder, or just call ./foo from the command line.