SKSpriteNode pools in iOS 8 seem to be allocated to overlapping memory - objective-c

I might be missing something. But my current app on the appstore works in iOS 7, but in iOS 8 completely fails because it won't create a preallocated pool of sprites. They appear to be written to the same address unless the sprites have specifically different properties.
In iOS 7 the following code produces a set with 4 unique objects. In iOS 8, the same code produces a set with only 1 object:
NSMutableSet *aSet = [NSMutableSet set];
SKColor *sameColor = [SKColor redColor];
CGSize sameSize = CGSizeMake(10, 10);
for (int i = 0; i < 4; i++) {
//allocate a brand new sprite
SKSpriteNode *thisSprite1 = [[SKSpriteNode alloc] initWithColor:sameColor size:sameSize];
[aSet addObject:thisSprite1];
}
NSLog(#"aSet Count: %i", aSet.count);
iOS8 Result:
2014-09-09 15:06:43.065 MSM[383:27490] aSet Count: 1
Am I going crazy? Amazingly, pretty much my entire app is based on this code concept repeated over and over again. If I do the same thing, but use something like NSObject, then the problem goes away, so it appears to be a new change to SKSprite. I know I can work around it with some crazy stuff, but is a huge pain, since I shouldn't have to do that, and I was hoping to avoid another version submission.

Thanks to Josh for the direction on how to solve this new bump in the road.
I subclassed SKSpriteNode, overriding -isEqual and -hash, to both be what my best guess at the NSObject implementation is. Then just did a Find/Replace All in Project for "SKSpriteNode" for my subclass name, and all is back to it was in the iOS 7 build:
-(BOOL)isEqual:(id)object{
return self == object;
}
- (NSUInteger)hash
{
return (NSUInteger)self;
}

Related

Reuse Same Sprite Cocos2d 3.0

I am wanting to use the same sprite multiple times in Cocos2d 3.0
at the moment when i do this i get the following error
"reason: 'child already added to another node. It can't be added again'"
i have looked at other responses to similar questions here on stacker overflow and they state that spriteBatchNode is required. However this has depreciated in version 3.0.
Make local instances of the sprites *( i have used nodes here as i get an error when trying to add them to the array (Incompatible pointer types assigning to 'CCSprite *' from 'CCNode '))
CCNode *_sprite1;
CCNode *_sprite2;
CCNode *_sprite3;
CCNode *_sprite4;
I currently Have an array where sprites(CCNodes)are added
_array1 = #[_sprite1,_sprite2,_sprite3,_sprite4];
_array2 = #[_sprite1,_sprite2,_sprite3,_sprite4];
I would then make a call to spawn the sprites
-(void)spawnSprites1
{
int randomNumber = [self generateRandomNumberBetweenMin:0 Max:_array1.count-1];
CCSprite *sprite = [_array1 objectAtIndex:randomNumber];
sprite.position = ccp(_size.width +100 , _size.height *0.26);
sprite.zOrder = DrawingOrderPlayer;
[_physicsNode addChild:sprite];
CCLOG(#"content size = %f",_size.height);
}
-(void)spawnSprites2
{
int randomNumber = [self generateRandomNumberBetweenMin:0 Max:_array2.count-1];
CCSprite *sprite = [_array2 objectAtIndex:randomNumber];
sprite.position = ccp(_size.width +100 , _size.height *0.26);
sprite.zOrder = DrawingOrderPlayer;
[_physicsNode addChild:sprite];
CCLOG(#"content size = %f",_size.height);
}
if the same sprite is drawn from either i receive the above crash log.
Does anybody know how to resolve this issue in Cocos2d v3.0
Thanks
Each new sprite needs to be a new CCSprite instance. Since cocos2d doesn't implement NSCopying yet, the easiest way is to initialize new sprites using one template instance's texture, like so:
CCSprite* newSprite = [CCSprite spriteWithTexture:templateSprite.texture];
Then assign any property from the templateSprite to newSprite if the copy should have the same value(s).
Problem resolved, rather the pulling sprites out of an array randomly, it was just as easy to randomly pull them using a switch statement.

Heap growth related to use of CCLabelTTF

I am using Cocos2d 1.01. I am having undesired heap growth. To identify what is causing the growth I took a baseline snapshot, did a state change and return to zero state and repeated the heapshot. I found the primary cause of the heap growth to be non-object and then looked at the stack trace, noting that the problematic code appeared to be centered around CCLabelTTF.
Here is the code that seems to be problematic:
NSString *desc = [pEffectDescriptions objectAtIndex:i];
CCLabelTTF *descrptionLabel = [CCLabelTTF labelWithString:desc dimensions:CGSizeMake(290, 65) alignment:UITextAlignmentLeft fontName:#"Verdana-Italic" fontSize:10];
descrptionLabel.anchorPoint = ccp(0,0);
descrptionLabel.color = ccc3(192, 192, 192);
descrptionLabel.position = ccp(aSprite.position.x + 8, aSprite.position.y);
[self addChild:descrptionLabel z:10 tag:COMPARTMENT0+9600+i];
I don't understand what the problem is, because before returning to state zero, the following code is executed:
for (int i=0; i<1000; i++) {
if ([self getChildByTag:COMPARTMENT0+9000+i])
[self removeChildByTag:COMPARTMENT0+9000+i cleanup:true];
}
My reasoning is that the CCLabelTTF is owned by the layer and it in turn owns the NSString (the array also retains the NSString). However, when I remove the CCLabelTTF from the layer and its dealloc gets called it should therefore release the CCLabelTTF, which would then dealloc. Could the array reference to the NSString be responsible for preventing the deallocation of CCLabelTTF?
Anyone have a clue?
While adding labels you add 9600 to the tag, while removing you only add 9000 to the tag. Could that be it?
PS: I suggest using bitmap fonts, they use less memory, create, update and render faster.

Cocos2d v2 + Chipmunk : Fails when adding shapes to a CCLayer

I'm very experienced with Cocos2d and Box2d, and wanted to try Chipmunk instead. Everything works fine as long as I add sprites to the helloworldlayer, but if I add another layer and attempt to add a sprite to that new layer it crashes.
It fails with an EXC_BAD_ACCESS in the cpSpaceStep function call in the update on the main layer.
-(void) update:(ccTime) delta
{
// Should use a fixed size step based on the animation interval.
int steps = 2;
CGFloat dt = [[CCDirector sharedDirector] animationInterval]/(CGFloat)steps;
for(int i=0; i<steps; i++){
HERE -> cpSpaceStep(space_, dt);
}
}
EDIT:
The problem seems to be regarding sprite batch nodes and not CCLayers.
Chipmunk doesn't actually interact directly with Cocos2D, it's just a physics engine. So if it's crashing in cpSpaceStep() with an EXC_BAD_ACCESS it's almost certainly because you gave Chipmunk a dangling pointer somewhere or have another memory bug that's corrupting data that Chipmunk is using.
If you compile it as debug, where does it crash exactly? Are you using any callbacks at all and are sure it's not happening in one of those?

Opening a gap in NSTableView during drag and drop

I've got a simple, single-column, view-based NSTableView with items in it that can be dragged to reorder them. During drag and drop, I'd like to make it so that a gap for the item-to-be-dropped opens up at the location under the mouse. GarageBand does something like this when you drag to reorder tracks (video here: http://www.screencast.com/t/OmUVHcCNSl). As far as I can tell, there's no built in support for this in NSTableView.
Has anyone else tried to add this behavior to NSTableView and found a good solution? I've thought of and tried a couple approaches without much success. My first thought was to double the height of the row under the mouse during a drag by sending -noteHeightOfRowsWithIndexesChanged: in my data source's -tableView:validateDrop:... method, then returning twice the normal height in -tableView:heightOfRow:. Unfortunately, best I can tell, NSTableView doesn't update its layout during drag and drop, so despite calling noteHeightOfRowsWithIndexesChanged:, the row height isn't actually updated.
Note that I'm using a view-based NSTableView, but my rows are not so complex that I couldn't move to a cell-based table view if doing so helped accomplish this. I'm aware of the easy, built-in ability to animate a gap for the dropped item after a drag is complete. I'm looking for a way to open a gap while the drag is in progress. Also, this is for an app to be sold in the Mac App Store, so it must not use private API.
EDIT: I've just filed an enhancement request with Apple requesting built in support for this behavior: http://openradar.appspot.com/12662624. Dupe if you'd like to see it too. Update: The enhancement I requested was implemented in OS X 10.9 Mavericks, and this behavior is now available using NSTableView API. See NSTableViewDraggingDestinationFeedbackStyleGap.
I feel bizarre for doing this, but there's an extremely thorough answer in the queue here that appears to have been deleted by its author. In it, they provided the correct links to a working solution, which I feel need to be presented as an answer for someone else to take and run with, inclusive of them if they desire to do so.
From the documentation for NSTableView, the following caveats are tucked away for row animation effects:
Row Animation Effects
Optional constant that specifies that the tableview will use a fade for row or column removal. The effect can be combined with any NSTableViewAnimationOptions constant.
enum {
NSTableViewAnimationEffectFade = 0x1,
NSTableViewAnimationEffectGap = 0x2,
};
Constants:
...
NSTableViewAnimationEffectGap
Creates a gap for newly inserted rows. This is useful for drag and drop animations that animate to a newly opened gap and should be used in the delegate method tableView:acceptDrop:row:dropOperation:.
Going through the example code from Apple, I find this:
- (void)_performInsertWithDragInfo:(id <NSDraggingInfo>)info parentNode:(NSTreeNode *)parentNode childIndex:(NSInteger)childIndex {
// NSOutlineView's root is nil
id outlineParentItem = parentNode == _rootTreeNode ? nil : parentNode;
NSMutableArray *childNodeArray = [parentNode mutableChildNodes];
NSInteger outlineColumnIndex = [[_outlineView tableColumns] indexOfObject:[_outlineView outlineTableColumn]];
// Enumerate all items dropped on us and create new model objects for them
NSArray *classes = [NSArray arrayWithObject:[SimpleNodeData class]];
__block NSInteger insertionIndex = childIndex;
[info enumerateDraggingItemsWithOptions:0 forView:_outlineView classes:classes searchOptions:nil usingBlock:^(NSDraggingItem *draggingItem, NSInteger index, BOOL *stop) {
SimpleNodeData *newNodeData = (SimpleNodeData *)draggingItem.item;
// Wrap the model object in a tree node
NSTreeNode *treeNode = [NSTreeNode treeNodeWithRepresentedObject:newNodeData];
// Add it to the model
[childNodeArray insertObject:treeNode atIndex:insertionIndex];
[_outlineView insertItemsAtIndexes:[NSIndexSet indexSetWithIndex:insertionIndex] inParent:outlineParentItem withAnimation:NSTableViewAnimationEffectGap];
// Update the final frame of the dragging item
NSInteger row = [_outlineView rowForItem:treeNode];
draggingItem.draggingFrame = [_outlineView frameOfCellAtColumn:outlineColumnIndex row:row];
// Insert all children one after another
insertionIndex++;
}];
}
I'm unsure if it's really this simple, but it's at least worth inspection and outright refutal if it doesn't meet your needs.
Edit: see this answer's comments for the steps followed to the right solution. The OP has posted a more complete answer, which should be referred to by anyone looking for solutions to the same problem.
Note: The behavior this question and answer describes are now available using built in API in NSTableView on OS X 10.9 Mavericks and later. See NSTableViewDraggingDestinationFeedbackStyleGap.
This answer may still be useful if this behavior is needed in an app targeting OS X 10.8 or earlier.
Original answer below:
I've implemented this now. My basic approach looks like this:
#interface ORSGapOpeningTableView : NSTableView
#property (nonatomic) NSInteger dropTargetRow;
#property (nonatomic) CGFloat heightOfDraggedRows;
#end
#implementation ORSGapOpeningTableView
#pragma mark - Dragging
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
NSInteger oldDropTargetRow = self.dropTargetRow;
NSDragOperation result = [super draggingUpdated:sender];
CGFloat imageHeight = [[sender draggedImage] size].height;
self.heightOfDraggedRows = imageHeight;
NSMutableIndexSet *changedRows = [NSMutableIndexSet indexSet];
if (oldDropTargetRow > 0) [changedRows addIndex:oldDropTargetRow-1];
if (self.dropTargetRow > 0) [changedRows addIndex:self.dropTargetRow-1];
[self noteHeightOfRowsWithIndexesChanged:changedRows];
return result;
}
- (void)draggingExited:(id<NSDraggingInfo>)sender
{
self.dropTargetRow = -1;
[self noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfRows])]];
[super draggingExited:sender];
}
- (void)draggingEnded:(id<NSDraggingInfo>)sender
{
self.dropTargetRow = -1;
self.heightOfDraggedRows = 0.0;
self.draggedRows = nil;
[self noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfRows])]];
}
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
self.dropTargetRow = -1;
self.heightOfDraggedRows = 0.0;
self.draggedRows = nil;
[self noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfRows])]];
return [super performDragOperation:sender];
}
// In my delegate and data source:
- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation
{
if (dropOperation == NSTableViewDropOn)
{
dropOperation = NSTableViewDropAbove;
[self.tableView setDropRow:++row dropOperation:dropOperation];
}
NSDragOperation result = [self.realDataSource tableView:tableView validateDrop:info proposedRow:row proposedDropOperation:dropOperation];
if (result != NSDragOperationNone)
{
self.tableView.dropTargetRow = row;
}
else
{
self.tableView.dropTargetRow = -1; // Don't open a gap
}
return result;
}
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
CGFloat result = [tableView rowHeight];
if (row == self.tableView.dropTargetRow - 1 && row > -1)
{
result += self.tableView.heightOfDraggedRows;
}
return result;
}
Note that this is simplified code, not a verbatim copy/paste from my program. I actually ended up making this all contained within an NSTableView subclass that uses proxy delegate and data source objects so the code in data source/delegate methods above is actually inside the proxies' intercept of the calls to the real delegate and data source. That way, the real data source and delegate don't have to do anything special to get the gap opening behavior. Also, there's sometimes a little flakiness with the table view animations, and this doesn't work for drags above the first row (no gap is opened since there's no row to make taller). All in all, despite the room for further improvement, this approach works reasonably well.
I'd still like to try a similar approach, but insert a blank row (as Caleb suggested) instead of changing the row height.
As of Mac OS X 10.9 (Mavericks), there's a much easier solution to animating drag & drop in a NSTableView:
[aTableView setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleGap];
The table view will automatically insert gaps with animation as a row is dragged which is much nicer than the old blue line insertion point method.
One way to accomplish what you're asking is to insert an empty row at the proposed drop point (that is, between the two nearest rows). It sounds like you've been looking at using NSTableViewAnimationEffectGap, which as you note is really meant for animating the insertion when the drop is accepted in -tableView:acceptDrop:row:dropOperation:.
Since you want to open up the gap before the user releases the mouse button to actually do the drop, you could instead insert a blank row using -insertRowsAtIndexes:withAnimation: from your table's -draggingUpdate: method and at the same time delete any blank row you previously inserted for this drag using -removeRowsAtIndexes:withAnimation:. Use NSTableViewAnimationSlideUp and NSTableViewAnimationSlideDown as the animations for these operations, as appropriate.

Collision detection using mulitple subviews within a moving view

I am working on a game in OBJ C that has a ball view and a stage view. The stage view has 4 subviews. All views are UIImageViews. I have a method for collision detection that is working. I would like to expand it to more than 4 subviews without simply creating more lines of code. Looking at the code below, is there a way to simplify this into loops instead. Thanks!
// convert each square to be relevant to ball's superview in order to collision detect
CGRect square_01Frame = [ball.superview convertRect:square_01.frame fromView:square_01.superview];
CGRect square_02Frame = [ball.superview convertRect:square_02.frame fromView:square_02.superview];
CGRect square_03Frame = [ball.superview convertRect:square_03.frame fromView:square_03.superview];
CGRect square_04Frame = [ball.superview convertRect:square_04.frame fromView:square_04.superview];
// convert CGRects to NSStrings for storage in square_frames array
NSString *square_01FrameString = NSStringFromCGRect(square_01Frame);
NSString *square_02FrameString = NSStringFromCGRect(square_02Frame);
NSString *square_03FrameString = NSStringFromCGRect(square_03Frame);
NSString *square_04FrameString = NSStringFromCGRect(square_04Frame);
// load array of NSStrings
[square_frames replaceObjectAtIndex:0 withObject:square_01FrameString];
[square_frames replaceObjectAtIndex:1 withObject:square_02FrameString];
[square_frames replaceObjectAtIndex:2 withObject:square_03FrameString];
[square_frames replaceObjectAtIndex:3 withObject:square_04FrameString];
// create a for loop
for (int i=0; i<4; i++) { // 4 squares
// create test frame
CGRect test_frame = CGRectFromString([square_frames objectAtIndex:i]);
if (CGRectIntersectsRect(test_frame,ball.frame)) { // collision detection
// do something
}
}
Well, I would do a number of things.
First, I would create a ball "model", just an NSObject subclass to represent the Ball. Probably, that would have a property "location" or something, which is the CGRect.
Then, your current view could have an array of ball objects on the screen, and just loop through them.
Overall, though, I don't think using UIView's rects is the best way to manage collision detection. I think you'd be better off defining that in some other way, and then simply updating the UI accordingly.
Generally, it's not a good idea to rely on your UI implementation for game design. It makes it hard to change (as you note in your question).