CCMenuItems loose tags - objective-c

I'm adding two CCMenuItemImage objects to a CCMenuItemToggle like so:
CCMenuItemImage *soundEnabled = [CCMenuItemImage itemWithNormalImage:#"button_sound_enabled.png"
selectedImage:#"button_sound_enabled.png"];
soundEnabled.tag = kSoundEnabled;
CCMenuItemImage *soundDisabled = [CCMenuItemImage itemWithNormalImage:#"button_sound_disabled.png"
selectedImage:#"button_sound_disabled.png"];
soundDisabled .tag = kSoundDisabled;
CCMenuItemToggle *sound = [CCMenuItemToggle itemWithItems:[NSArray arrayWithObjects:soundEnabled,soundDisabled,nil] block:^(id sender) {
CCMenuItem *item= ((CCMenuItemToggle*).sender).selectedItem;
CCLog(#"item tag: %d",item.tag);
}];
kSoundEnabled and kSoundDisabled are enumeration items with the values 2 and 3. When I log the tag of each CCMenuItemImage after i created them, everything is fine. But when I log them inside the block, the tags show up as -1061138431.
Also when I try to log them outside of the block, just a but further down in the init code of my layer they start to turn up wrong.
Does anybody know what the issue is here? It's a Kobold2d ARC-enabled project, could ARC be the issue here? I thought this wouldn't account for simple datatypes like NSInteger?
I know I could just check for sender.selectedIndex = 0 or sender.selectedIndex = 1 but I'd still like to understand what's the issue here.

The issue does not come from the ARC configuration or others. I tested it with Cocos2D 2.0 and I have the same issue. I checked the sources and the problem comes from the CCMenuItemToggle which change the tag of the children to keep the track of the current display item.
I should you to use the reference of your variable into your block like that:
CCMenuItemToggle *sound = [CCMenuItemToggle itemWithItems:[NSArray arrayWithObjects:soundEnabled,soundDisabled,nil] block:^(id sender)
{
CCMenuItem *item= ((CCMenuItemToggle*).sender).selectedItem;
if (item == soundEnabled)
{
//...
} else
{
//...
}
}];

Related

CCActionDelay not working as expected

What I have is a method that creates CCSprites:
-(void)createDebrisAtPosition:(CGPoint)position{
NSInteger numberOfPieces = [random randomWithMin:5 max:20];
for (int i=0; i<numberOfPieces; i++) {
CCSprite *debris = [CCSprite spriteWithImageNamed:#"debri.png"];
debris.position = position;
debris.physicsBody = [CCPhysicsBody bodyWithRect:CGRectMake(0, 0, debris.contentSize.width, debris.contentSize.height) cornerRadius:0];
debris.physicsBody.collisionType = #"debris";
debris.name = #"Debris";
CCActionRemove *removeAction = [CCActionRemove action];
CCActionSequence *sequence = [CCActionSequence actions:[CCActionDelay actionWithDuration:2.0], removeAction, nil];
[physics addChild:debris];
//physics is a CCPhysicsNode here
[debris runAction:sequence];
}
}
This method then gets invoked during specific collision events:
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair enemy:(EnemyNode*)enemy projectile:(ProjectileNode*)projectile
{
[enemy removeFromParent];
[projectile removeFromParent];
[self createDebrisAtPosition:enemy.position];
return NO;
}
Expected behavior: CCSprites should appear and then get removed only after 2.0 secs.
Actual behavior: CCSprites appear for a split-second, then instantly get removed.
I also tried CCActionInterval, CCActionEaseOut, but they didn't work (And they shouldn't, according to the docs, but CCActionDelay — should, but not working). I changed the order of method invocation (runAction after and before addChild), as well as the order of action invocation this didn't work as well. Don't mind the CCActionDelay declaration directly in the CCActionSequence — I tried to declare it as a separate variable, with zero luck.
What am I misunderstanding here?
I'm new here so I'm not allowed to comment yet (this would perhaps be better suited as a comment) but: I haven't been able to recreate your problem. The problem is not related to CCActionDelay or the actions you are running on the debris sprite. You can test this yourself by running your sequence in a different setup. Ergo: there must be a problem somewhere else in your code. I'm sorry, but I cannot help any further based on the example code you've posted.

NSOpenPanel (doesn't) validateVisibleColumns

I have an NSOpenPanel with an accessoryView; in this view the user chooses a couple of radio button to change the allowed types. When the panel opens, the right files are enabled, the other disabled. Ok, good.
Now the user changes the radio buttons, the viewController of the accessoryView observe the changes in the radio button matrix and changes consequently the allowedTypes of the NSOpenPanel.
After that, following Apple documentation, it calls -validateVisibleColumns, but nothing visible changes in the panel. That is: the right files seems disabled: I can choose them but they are in grey!
Another wrong effect: I select a file (enabled), change the file type, the (now wrong) file remains selected, with the OK button enabled: but this is the wrong file type! It seems that the change happens but the interface doesn't know!
My code is (selected is bound to the matrix of radio button):
- (void)observeValueForKeyPath.....
{
NSString *extension = (self.selected==0) ? #"txt" : #"xml";
[thePanel setAllowedFileTypes:#[extension, [extension uppercaseString]]];
[thePanel validateVisibleColumns];
}
I first tried to insert a call
[thePanel displayIfNeeded]
then I tried with
[thePanel contentView] setNeedsDisplay]
with no results. I also tried to implement the panel delegate method panel:shouldEnableURL:, that should be called by validateVisibleColumns: I just found that it was called just once, at the opening of NSOpenPanel.
Can someone have an idea why this happens? I tried all this with sandboxed and not-sandboxed applications, no difference. I'm developing on ML with 10.8 sdk.
Edit
By now the only way to avoid the problem is to implement panel:validateURL:error, but this is called after the user clicked 'open' and it's very bad.
I have the exact same problem, under 10.9, non-sandboxed, and have spent the better part of this DAY trying to find a solution!
After A LOT of tinkering and drilling down through the various classes that make up the NSOpenPanel (well NSSavePanel really) I did find a way to force the underlying table to refresh itself:
id table = [[[[[[[[[[[[_openPanel contentView] subviews][4] subviews][0] subviews][0] subviews][0] subviews][7] subviews][0] subviews][1] subviews][0] subviews][0] subviews][0] subviews][2];
[table reloadData];
Of course, the best way to code this hack would be to walk down the subview list ensuring the right classes are found and eventually caching the end table view for the subsequent reloadData calls.
I know, I know, this is a very ugly kludge, however, I can not seem to find any other answer to fix the issue, other than "file a bug report". Which, from what I can see online people have been doing since 1.8! :(
EDIT:
Here is the code I am now using to make my NSOpenPanel behave correctly under 10.9:
- (id) openPanelFindTable: (NSArray*)subviews;
{
id table = nil;
for (id view in subviews) {
if ([[view className] isEqualToString: #"FI_TListView"]) {
table = view;
break;
} else {
table = [self openPanelFindTable: [view subviews]];
if (table != nil) break;
}
}
return table;
}
- (void) refreshOpenPanel
{
if (_openPanelTableHack == nil)
_openPanelTableHack = [self openPanelFindTable: [[_openPanel contentView] subviews]];
[_openPanelTableHack reloadData];
[_openPanel validateVisibleColumns];
}
This code requires two instance variables _openPanel and _openPanelTableHack to be declared in order to work. I declared _openPanel as NSOpenPanel* and _openPanelTableHack is declared as id.
Now, instead of calling [_openPanel validateVisibleColumns] I call [self refreshOpenPanel] to force the panel to update the filenames as expected. I tried caching the table view when the NSOpenPanel was created, however, it seems that once you "run" the panel the table view changes, so I have to cache it on the first update instead.
Again, this is a GIANT hack, however, I do not know how long it will take Apple to fix the issue with accessory views and the file panels, so for now, this works.
If anyone has any other solutions that are not huge kludges please share! ;)
An implementation in swift of Eidola solution.
Biggest difference is that I search for a NSBrowser (sub)class rather than a specific class name. Tested on 10.10 (not sandboxed).
private weak var panelBrowser : NSBrowser? //avoid strong reference cycle
func reloadBrowser()
{
if let assumedBrowser = panelBrowser
{
assumedBrowser.reloadColumn(assumedBrowser.lastColumn)
}
else if let searchResult = self.locateBrowser(self.panel?.contentView as! NSView)
{
searchResult.reloadColumn(searchResult.lastColumn)
self.panelBrowser = searchResult //hang on to result
}
else
{
assertionFailure("browser not found")
}
}
//recursive search function
private func locateBrowser(view: NSView) -> NSBrowser?
{
for subview in view.subviews as! [NSView]
{
if subview is NSBrowser
{
return subview as? NSBrowser
}
else if let result = locateBrowser(subview)
{
return result
}
}
return nil
}
Edit:
Ok, so the code above will not work all the time. If it's not working and a file is selected (you can see the details/preview), then you have to reload the last to one column instead of the last column. Either reload the last two columns (make sure there are at least 2 columns) or reload all columns.
Second problem: if you reload the column, then you lose the selection. Yes, the selected files/directory will still be highlighted, but the panel will not return the correct URL's.
Now I am using this function:
func reloadBrowser()
{
//obtain browser
if self.panelBrowser == nil
{
self.panelBrowser = self.locateBrowser(self.panel?.contentView as! NSView)
}
assert(panelBrowser != nil, "browser not found")
//reload browser
let panelSelectionPatch = panelBrowser.selectionIndexPaths //otherwise the panel return the wrong urls
if panelBrowser.lastColumn > 0
{
panelBrowser.reloadColumn(panelBrowser.lastColumn-1)
}
panelBrowser.reloadColumn(panelBrowser.lastColumn)
panelBrowser.selectionIndexPaths = panelSelectionPatch
}
Just upgrade to xcode 6.3 (on Yosemite 10.10.3) and its ok. Apple fixed the bug (no more need Eidola Hack).

Different Behavior Between Debug and Release Builds

I'm using the SOCKit library to implement a URL router for my app. I have a custom Router class that keeps track of all the valid routes and implements a match method that, given a route NSString, matches it to a corresponding view controller. To make things easier, the matchable view controllers must implement the Routable protocol, which requires an initWithState: method that takes an NSDictionary as a parameter. Here's the relevant code:
- (id)match:(NSString *)route
{
for (NSArray *match in routePatterns) {
const SOCPattern * const pattern = [match objectAtIndex:kPatternIndex];
if ([pattern stringMatches:route]) {
Class class = [match objectAtIndex:kObjectIndex];
NSLog(#"[pattern parameterDictionaryFromSourceString:route]: %#", [pattern parameterDictionaryFromSourceString:route]);
UIViewController<Routable> *vc;
vc = [[class alloc] initWithState:[pattern parameterDictionaryFromSourceString:route]];
return vc;
}
}
return nil;
}
When I run the app with the debug configuration, [pattern parameterDictionaryFromSourceString:route] produces what is expected:
[pattern parameterDictionaryFromSourceString:route]: {
uuid = "e9ed6708-5ad5-11e1-91ca-12313810b404";
}
On the other hand, when I run the app with the release configuration, [pattern parameterDictionaryFromSourceString:route] produces an empty dictionary. I'm really not sure how to debug this. I've checked my own code to see if there are any obvious differences between the debug and release builds to no avail and have also looked at the SOCKit source code. Ideas? Thanks!
I just ran into this issue myself today. The issue in my case was that Release builds blocked assertions, but in -performSelector:onObject:sourceString: and -parameterDictionaryFromSourceString: is this important line:
NSAssert([self gatherParameterValues:&values fromString:sourceString],
#"The pattern can't be used with this string.");
Which, when assertions are converted to no-ops, vanishes, and the gathering never happens. With no parameter values, not much happens! I changed it to the following (and will submit an issue to the GitHub repo):
if( ![self gatherParameterValues:&values fromString:sourceString] ) {
NSAssert(NO, #"The pattern can't be used with this string.");
return nil;
}
EDIT: reported as issue #13.

Check if an action is currently running?

Is it possible to check if there are actions currently running in a CCNode class in Cocos2d? I'd like to know if a CCMoveBy is still running or not.
You can use [self numberOfRunningActions] on any CCNode. In your case, it sounds like you want to know if there are simply any actions running or not, so it's not a big deal to know the exact number beforehand.
We can easily check if specific actions run by using getActionByTag method and action.tag property.
There is no need to introduce the CCCallFuncN callbacks or counting numberOfRunningActions.
Example.
In our app it is important to let the jumpAction to be finished prior to executing another jump. To prevent triggering another jump during an already running jump action
the critical jump section of code is protected as follows:
#define JUMP_ACTION_TAG 1001
-(void)jump {
// check if the action with tag JUMP_ACTION_TAG is running:
CCAction *action = [sprite getActionByTag:JUMP_ACTION_TAG];
if(!action) // if action is not running execute the section below:
{
// create jumpAction:
CCJumpBy *jumpAction = [CCJumpBy actionWithDuration:jumpDuration position:ccp(0,0) height:jumpHeight jumps:1];
// assign tag JUMP_ACTION_TAG to the jumpAction:
jumpAction.tag = JUMP_ACTION_TAG;
[sprite runAction:jumpAction]; // run the action
}
}
You can always add a method to indicate when the method is finished, and then toggle some BOOL or something like that to indicate it is not running, and put a start method to toggle the BOOL to indicate it started:
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(-target.contentSize.width/2, actualY)];
id actionMoveDone = [CCCallFuncN actionWithTarget:self
selector:#selector(spriteMoveFinished:)];
id actionMoveStarted = [CCCallFuncN actionWithTarget:self
selector:#selector(spriteMoveStarted:)];
[target runAction:[CCSequence actions:actionMoveStarted, actionMove, actionMoveDone, nil]];
Modified from here.
In the two #selector methods:
-(void) spriteMoveStarted:(id)sender {
ccMoveByIsRunning = YES;
}
and:
-(void) spriteMoveFinished:(id)sender {
ccMoveByIsRunning = NO;
}
where ccmoveByIsRunning is the BOOL I'm referring to.
EDIT: As xus has pointed out, you should actually not do this and instead use [self numberOfRunningActions] as others have pointed out.

Bindings with NSManagedObject from child context only working for NEW objects

Background:
In my app, I'm specifically targeting Mac OS X Lion. This issue involves Core Data, an NSPopover and a child NSManagedObjectContext (created by using the new parentContext property of NSManagedObjectContext).
I have a table of NSManagedObjects of class "Location". There's an Add button that calls addLocation: and if a table row is double-clicked, I call tableViewDoubleClick:.
For either case, what I do is create a new NSManagedObjectContext and set its parent context to that of the document's context. I then either create a new Location in that context or fetch the Location to be edited from the temporary context. I set the popover's representedObject property to the location in question. If I cancel the popover, nothing is saved. If the user clicks a Save button in the popover, I just call save: on the temporary context and the changes get pushed to the main context.
addLocation:
- (IBAction)addLocation:(id)sender
{
LocationEditViewController *popupController = [[[LocationEditViewController alloc] init] autorelease];
popupController.title = #"Add New Location";
NSManagedObjectContext *tempContext = [[[NSManagedObjectContext alloc] init] autorelease];
tempContext.parentContext = self.document.managedObjectContext;
Location *tempLocation = [NSEntityDescription insertNewObjectForEntityForName:#"Location" inManagedObjectContext:tempContext];
popupController.representedObject = tempLocation;
popupController.managedObjectContext = tempContext;
[popupController.popover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
}
tableViewDoubleClick:
- (void)tableViewDoubleClick:(id)sender
{
NSInteger selectedRow = [self.table selectedRow];
if (selectedRow != -1)
{
NSRect rectOfSelectedRow = [self.table rectOfRow:selectedRow];
LocationEditViewController *popupController = [[[LocationEditViewController alloc] init] autorelease];
popupController.title = #"Edit Location";
Location *locationToEdit = [self.locationController.selectedObjects objectAtIndex:0];
NSManagedObjectContext *tempContext = [[[NSManagedObjectContext alloc] init] autorelease];
tempContext.parentContext = self.document.managedObjectContext;
Location *tempLocation = (Location *)[tempContext fetchObjectEqualTo:locationToEdit]; // Custom fetch helper method
popupController.managedObjectContext = tempContext;
popupController.representedObject = tempLocation;
[popupController.popover showRelativeToRect:rectOfSelectedRow ofView:sender preferredEdge:NSMaxXEdge];
}
}
Here's the problem that I'd like an explanation for:
The text fields in the popover are connected to the popover's representedObject via bindings in the nib. These work perfectly with a new object (addLocation:).
If the Location is an existing object (tableViewDoubleClick:), the bindings work well enough to pre-populate the fields with the Location's properties. However, changing the text in the fields does not alter the Location's properties at all. When the Save button in the popup is clicked, I tried logging the Location's properties before saving the temporary context. If it's an existing object, whatever I type into the fields isn't being reflected in the Location's properties - as if the bindings are only communicating one-way.
My workaround: I found that if I skip the bindings and just manually set the Location's properties to the values in the text fields before the save, that the changes do take effect.
- (IBAction)popoverSave:(id)sender
{
// These two methods always work. But if I remove these and use bindings instead, it only works for NEW Locations.
[(Location *)self.representedObject setLabel:self.labelField.stringValue];
[(Location *)self.representedObject setLocation:self.locationField.stringValue];
NSLog(#"representedObject = %#", self.representedObject);
NSError *error = nil;
[self.managedObjectContext save:&error];
[self.popover close];
}
I'd really like to know why this is the case, just in case I'm actually doing something wrong.
Thanks!
I think it's likely that it is the cast in these lines:
[(Location *)self.representedObject setLabel:self.labelField.stringValue];
[(Location *)self.representedObject setLocation:self.locationField.stringValue];
… that makes them work. If so, then you probably have a NSObject or NSManagedObject set somewhere in the bindings as the class instead of the Location class. When the binding sends a Location class specific message e.g. set an attribute with a specific name, to the generic class, the generic class silently ignores the message.
BTW, I would caution against using multiple context instead of using the undo API. I see a lot of people get in trouble that way. It's easier to roll back a single context than it is to manage multiple context.