Getting memory warnings after starting a new Cocos2d-iPhone project in ARC. Beginner - objective-c

Have recently been teaching myself objective-c in order to learn Cocos2d.
Started a new project to teach myself the basics with a classic falling gems tetris-like game. The first step I took was to enable the project for ARC using edit > refactor, and selecting the last 4 files in the template.
So far have been able to add a single gem with a dynamically colored sprite through subclassing CCSprite, and move it along the XCoordinates while having it fall from the top. The gems are given a gemPos location – eg 5,3 – in order for me to later implement the matching methods.
Am getting an EXC_BAD_ACCESS warning when a few of the blocks stack up on top of each other after testing out the project for a while. Am confused as to why this is happening if ARC is enabled.
My clumsy code is as follows (have left out .h files):
Default helloworld layer:
-(id) init
{
if( (self=[super init]) ) {
oldGems = [[NSMutableArray alloc]init];
[self setIsTouchEnabled:YES];
[self newGem];
[self scheduleUpdate];
}
return self;
}
- (void)registerWithTouchDispatcher
{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
oldPos = touchLocation;
return TRUE;
}
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
[self updateXCoord:(CGPoint)touchLocation];
}
- (void)updateXCoord:(CGPoint)touchLocation
{
CGPoint distance = ccpSub(touchLocation, oldPos);
float xDistance = distance.x;
if (abs(xDistance) >= 36) {
if (touchLocation.x > oldPos.x) {
[_thisGem setPosition:ccp([_thisGem position].x + 36, [_thisGem position].y)];
[_thisGem setGemPos:ccp([_thisGem gemPos].x+1,[_thisGem gemPos].y)];
NSLog(#"The gem position is %#", NSStringFromCGPoint([_thisGem gemPos]));
oldPos = touchLocation;
} else {
[_thisGem setPosition:ccp([_thisGem position].x - 36, [_thisGem position].y)];
[_thisGem setGemPos:ccp([_thisGem gemPos].x-1,[_thisGem gemPos].y)];
NSLog(#"The gem position is %#", NSStringFromCGPoint([_thisGem gemPos]));
oldPos = touchLocation;
}
}
}
- (void)newGem
{
if (_thisGem) {
PCGem *oldGem = _thisGem;
NSLog(#"Old gem position at %#", NSStringFromCGPoint([oldGem gemPos]));
[oldGems addObject:oldGem];
}
_thisGem = [[PCGem alloc] initWithGemColor];
[_thisGem setPosition:ccp(160, 450)];
[self addChild:_thisGem];
[_thisGem setGemPos:ccp(4,10)];
NSLog(#"Gem added with %# color", [_thisGem gemColorName]);
}
- (void)update:(ccTime)dt
{
if ([_thisGem gemPos].y > 0) {
[self spaceBelowOccupied];
[_thisGem setPosition:ccp([_thisGem position].x, [_thisGem position].y-1)];
[_thisGem setGemPos:ccp([_thisGem gemPos].x, floor([_thisGem position].y / 36))];
} else {
[self newGem];
}
}
- (void)spaceBelowOccupied
{
for (PCGem *occupiedTile in oldGems) {
if (CGRectIntersectsRect(occupiedTile.boundingBox, _thisGem.boundingBox)) {
NSLog(#"Collision detected!");
[self newGem];
}
}
}
And my clumsy PCGem class:
- (id)initWithGemColor
{
if ((self = [super initWithFile:#"gemGS.png"])) {
NSArray *gemColorList = [NSArray arrayWithObjects:(NSString *)#"blue", (NSString *)#"red", (NSString *)#"green", nil];
NSInteger randColor = arc4random_uniform(3);
switch (randColor) {
case 0:
{
[self setGemColorName:[gemColorList objectAtIndex:0]];
ccColor3B gemColorRGB = {0,0,255};
[self setColor:gemColorRGB];
break;
}
case 1:
{
[self setGemColorName:[gemColorList objectAtIndex:1]];
ccColor3B gemColorRGB = {255,0,0};
[self setColor:gemColorRGB];
break;
}
case 2:
{
[self setGemColorName:[gemColorList objectAtIndex:2]];
ccColor3B gemColorRGB = {0,255,0};
[self setColor:gemColorRGB];
break;
}
}
}
return self;
}
Which leads me to another question, which I can't seem to find the answer to. In examples of ARC enabled Cocos2d projects, I've seen the node convenience method used, which of course is alloc]init]autorelease]. But I thought you can't use Autorelease with ARC and this would cause a crash? How does this work?
Any ideas to my woes? Would greatly appreciate clarification :)
Cheers,
Adrian

I think it might be caused by editing an array while enumerating through it. In your update function you call spaceBelowOccupied:
- (void)spaceBelowOccupied
{
for (PCGem *occupiedTile in oldGems) {
if (CGRectIntersectsRect(occupiedTile.boundingBox, _thisGem.boundingBox)) {
NSLog(#"Collision detected!");
[self newGem];
}
}
}
then if newGem gets called while in your for loop and this loop succeeds:
if (_thisGem) {
PCGem *oldGem = _thisGem;
NSLog(#"Old gem position at %#", NSStringFromCGPoint([oldGem gemPos]));
[oldGems addObject:oldGem];
}
then you are adding an object to an array while enumerating through it. So change your spaceBelowOccupied to this and see if it works:
- (void)spaceBelowOccupied
{
for (PCGem *occupiedTile in [oldGems copy]) {
if (CGRectIntersectsRect(occupiedTile.boundingBox, _thisGem.boundingBox)) {
NSLog(#"Collision detected!");
[self newGem];
}
}
}
that way you make a copy of oldGems to enumerate through that will get autoreleased once you are done going through it.

Related

Score system in Cocos2d project

I have a game where the user collects different types of objects and a label that has a value of 0 at the start. Every time the user collects an object (by touching it) it should make the score = current score + 1; I have tried with the following code but it crashes when I click on the object.
This is the code for my score label which puts a 0 on the screen:
score = 0;
scoreLabel1 = [CCLabelTTF labelWithString:#"0" fontName:#"Times New Roman" fontSize:33];
scoreLabel1.position = ccp(240, 160);
[self addChild:scoreLabel1 z:1];
And this is the void function which I call every time I touch an object:
- (void) addScore
{
score = score + 1;
[scoreLabel1 setString:[NSString stringWithFormat:#"%#", score]];
}
And this is the actual part where I put the code for touching the object:
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self ccTouchesMoved:touches withEvent:event];
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
for (Apple in self.appleArray)
{
if (CGRectContainsPoint(Apple.boundingBox, location))
{
[self addScore];
Apple.visible = NO;
}
}
Everything else works except for the score. Also is there a way to make the apple disappear instead of just making it invisible by apple.visible = false? because this way the apple is still there but not visible, I want to get rid of it.
Hope some one can help!
If you have any questions let me know.
Thanks.
This is where I draw the apples:
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if( (self=[super init]) ) {
isTouchEnabled_ = YES;
self.appleArray = [CCArray arrayWithCapacity:20];
for (int i = 0; i < 5; i++) {
Apple = [CCSprite spriteWithFile:#"Apple4.png"];
[self addChild:Apple];
[appleArray addObject:Apple];
}
[Apple removeFromParentAndCleanup:true];
[self scheduleUpdate];
}
return self;
}
And this is where the screen gets updated:
-(void) update: (ccTime) dt
{
for (int i = 0; i < 5; i++) {
Apple = ((CCSprite *)[appleArray objectAtIndex:i]);
if (Apple.position.y > -250) {
Apple.position = ccp(Apple.position.x, Apple.position.y - (Apple.tag*dt));
}
}
}
a couple of things here. In setScore, your format is broken and will cause a crash (%# requires an NSObject*). Try:
[scoreLabel1 setString:[NSString stringWithFormat:#"%i", score]];
also, the syntax of your for loop is odd. Try
for (Apple *anyApple in self.appleArray)
{
if (CGRectContainsPoint(anyApple.boundingBox, location))
{
if (anyApple.visible) {
[self addScore];
anyApple.visible = NO;
}
}
}
score = score + 1;
[scoreLabel1 setString:[NSString stringWithFormat:#"%#", score]];
Please read this: String Format Specifiers
%# - Objective-C object, printed as the string returned by descriptionWithLocale: if available, or description otherwise. Also works with CFTypeRef objects, returning the result of the CFCopyDescription function.
If your score is an object, you can't "increment" its value that way. If it's an int or a float, you are using wrong format specifier.

How do I animate fadeIn fadeOut effect in NSTextField when changing its text?

I am trying to write a category for NSTextField which will add a new method setAnimatedStringValue. This method is supposed to nicely fade-out the current text, then set the new text and then fade that in.
Below is my implementation:-
- (void) setAnimatedStringValue:(NSString *)aString {
if ([[self stringValue] isEqualToString:aString]) {
return;
}
NSMutableDictionary *dict = Nil;
NSViewAnimation *fadeOutAnim;
dict = [NSDictionary dictionaryWithObjectsAndKeys:self, NSViewAnimationTargetKey,
NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, nil];
fadeOutAnim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:
dict, nil]];
[fadeOutAnim setDuration:2];
[fadeOutAnim setAnimationCurve:NSAnimationEaseOut];
[fadeOutAnim setAnimationBlockingMode:NSAnimationBlocking];
NSViewAnimation *fadeInAnim;
dict = [NSDictionary dictionaryWithObjectsAndKeys:self, NSViewAnimationTargetKey,
NSViewAnimationFadeInEffect, NSViewAnimationEffectKey, nil];
fadeInAnim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:
dict, nil]];
[fadeInAnim setDuration:3];
[fadeInAnim setAnimationCurve:NSAnimationEaseIn];
[fadeInAnim setAnimationBlockingMode:NSAnimationBlocking];
[fadeOutAnim startAnimation];
[self setStringValue:aString];
[fadeInAnim startAnimation];
}
Needless to say, but the above code does not work at all. The only effect I see is the flickering of a progress bar on the same window. That is possibly because I am blocking the main runloop while trying to "animate" it.
Please suggest what is wrong with the above code.
Additional note:
setAnimatedStringValue is always invoked by a NSTimer, which is added to the main NSRunLoop.
I was poking around a bit after posting the previous answer. I'm leaving that answer because it corresponds closely to the code you posted, and uses NSViewAnimation. I did, however, come up with a considerably more concise, albeit slightly harder to read (owing to block parameter indentation) version that uses NSAnimationContext instead. Here 'tis:
#import <QuartzCore/QuartzCore.h>
#interface NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString;
#end
#implementation NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString
{
if ([[self stringValue] isEqual: aString])
{
return;
}
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setDuration: 1.0];
[context setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
[self.animator setAlphaValue: 0.0];
}
completionHandler:^{
[self setStringValue: aString];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setDuration: 1.0];
[context setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn]];
[self.animator setAlphaValue: 1.0];
} completionHandler: ^{}];
}];
}
#end
Note: To get access to the CAMediaTimingFunction class used here for specifying non-default timing functions using this API, you'll need to include QuartzCore.framework in your project.
Also on GitHub.
For Swift 3, here are two convenient setText() and setAttributedText() extension methods that fade over from one text to another:
import Cocoa
extension NSTextField {
func setStringValue(_ newValue: String, animated: Bool = true, interval: TimeInterval = 0.7) {
guard stringValue != newValue else { return }
if animated {
animate(change: { self.stringValue = newValue }, interval: interval)
} else {
stringValue = newValue
}
}
func setAttributedStringValue(_ newValue: NSAttributedString, animated: Bool = true, interval: TimeInterval = 0.7) {
guard attributedStringValue != newValue else { return }
if animated {
animate(change: { self.attributedStringValue = newValue }, interval: interval)
}
else {
attributedStringValue = newValue
}
}
private func animate(change: #escaping () -> Void, interval: TimeInterval) {
NSAnimationContext.runAnimationGroup({ context in
context.duration = interval / 2.0
context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animator().alphaValue = 0.0
}, completionHandler: {
change()
NSAnimationContext.runAnimationGroup({ context in
context.duration = interval / 2.0
context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
self.animator().alphaValue = 1.0
}, completionHandler: {})
})
}
}
Call them as following:
var stringTextField: NSTextField
var attributedStringTextField: NSTextField
...
stringTextField.setStringValue("New Text", animated: true)
...
let attributedString = NSMutableAttributedString(string: "New Attributed Text")
attributedString.addAttribute(...)
attributedStringTextField.setAttributedStringValue(attributedString, animated: true)
I'll take a stab:
I found a couple problems here. First off, this whole thing is set up to be blocking, so it's going to block the main thread for 5 seconds. This will translate to the user as a SPOD/hang. You probably want this to be non-blocking, but it'll require a little bit of extra machinery to make that happen.
Also, you're using NSAnimationEaseOut for the fade out effect, which is effected by a known bug where it causes the animation to run backwards. (Google for "NSAnimationEaseOut backwards" and you can see that many have hit this problem.) I used NSAnimationEaseIn for both curves for this example.
I got this working for a trivial example with non-blocking animations. I'm not going to say that this is the ideal approach (I posted a second answer that arguably better), but it works, and can hopefully serve as a jumping off point for you. Here's the crux of it:
#interface NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString;
#end
#interface SOTextFieldAnimationDelegate : NSObject <NSAnimationDelegate>
- (id)initForSettingString: (NSString*)newString onTextField: (NSTextField*)tf;
#end
#implementation NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString
{
if ([[self stringValue] isEqual: aString])
{
return;
}
[[[SOTextFieldAnimationDelegate alloc] initForSettingString: aString onTextField: self] autorelease];
}
#end
#implementation SOTextFieldAnimationDelegate
{
NSString* _newString;
NSAnimation* _fadeIn;
NSAnimation* _fadeOut;
NSTextField* _tf;
}
- (id)initForSettingString: (NSString*)newString onTextField: (NSTextField*)tf
{
if (self = [super init])
{
_newString = [newString copy];
_tf = [tf retain];
[self retain]; // we'll autorelease ourselves when the animations are done.
_fadeOut = [[NSViewAnimation alloc] initWithViewAnimations: #[ (#{
NSViewAnimationTargetKey : tf ,
NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect})] ];
[_fadeOut setDuration:2];
[_fadeOut setAnimationCurve: NSAnimationEaseIn];
[_fadeOut setAnimationBlockingMode:NSAnimationNonblocking];
_fadeOut.delegate = self;
_fadeIn = [[NSViewAnimation alloc] initWithViewAnimations: #[ (#{
NSViewAnimationTargetKey : tf ,
NSViewAnimationEffectKey : NSViewAnimationFadeInEffect})] ];
[_fadeIn setDuration:3];
[_fadeIn setAnimationCurve:NSAnimationEaseIn];
[_fadeIn setAnimationBlockingMode:NSAnimationNonblocking];
[_fadeOut startAnimation];
}
return self;
}
- (void)dealloc
{
[_newString release];
[_tf release];
[_fadeOut release];
[_fadeIn release];
[super dealloc];
}
- (void)animationDidEnd:(NSAnimation*)animation
{
if (_fadeOut == animation)
{
_fadeOut.delegate = nil;
[_fadeOut release];
_fadeOut = nil;
_tf.hidden = YES;
[_tf setStringValue: _newString];
_fadeIn.delegate = self;
[_fadeIn startAnimation];
}
else
{
_fadeIn.delegate = nil;
[_fadeIn release];
_fadeIn = nil;
[self autorelease];
}
}
#end
It would be really nice if there were block-based API for this... it'd save having to implement this delegate object.
I put the whole project up on GitHub.

tapAtPoint on UIWebView subclass

I have subclassed UIWebView so that I can get touch events and also implemented this handy method. I'm curious, if this will work on an actual iOS device. I'm not at the office, so I don't know if does. It seems to work in the simulator.
- (void) tapAtPoint:(CGPoint)point
{
id /*UIWebBrowserView*/ webBrowserView = nil;
id webViewInternal = nil;
object_getInstanceVariable(self, "_internal", (void **)&webViewInternal);
object_getInstanceVariable(webViewInternal, "browserView", (void **)&webBrowserView);
if (webBrowserView) {
[webBrowserView tapInteractionWithLocation:point];
}
}
Has anyone tried something like this? I for sure find out in the morning, lol.
Please try this code, Here its working fine.
/* TapDetectingWindow.m */
#import "TapDetectingWindow.h"
#implementation TapDetectingWindow
#synthesize viewToObserve;
#synthesize controllerThatObserves;
- (id)initWithViewToObserver:(UIView *)view andDelegate:(id)delegate {
if(self == [super init]) {
self.viewToObserve = view;
self.controllerThatObserves = delegate;
}
return self;
}
- (void)dealloc {
[viewToObserve release];
[super dealloc];
}
- (void)forwardTap:(id)touch {
[controllerThatObserves userDidTapWebView:touch];
}
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
if (viewToObserve == nil || controllerThatObserves == nil)
return;
NSSet *touches = [event allTouches];
if (touches.count != 1)
return;
UITouch *touch = touches.anyObject;
if (touch.phase != UITouchPhaseEnded)
return;
if ([touch.view isDescendantOfView:viewToObserve] == NO)
return;
CGPoint tapPoint = [touch locationInView:viewToObserve];
NSLog(#"TapPoint = %f, %f", tapPoint.x, tapPoint.y);
NSArray *pointArray = [NSArray arrayWithObjects:[NSString stringWithFormat:#"%f", tapPoint.x],
[NSString stringWithFormat:#"%f", tapPoint.y], nil];
if (touch.tapCount == 1) {
[self performSelector:#selector(forwardTapwithObject:pointArray afterDelay:0.5];
}
else if (touch.tapCount > 1) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(forwardTap  object:pointArray];
}
}
#end
/* WebViewController.h */
#interface WebViewController : UIViewController<TapDetectingWindowDelegate> {
IBOutlet UIWebView *mHtmlViewer;
TapDetectingWindow *mWindow;
}
/* WebViewController.m */
- (void)viewDidLoad {
[super viewDidLoad];
mWindow = (TapDetectingWindow *)[[UIApplication sharedApplication].windows objectAtIndex:0];
mWindow.viewToObserve = mHtmlViewer;
mWindow.controllerThatObserves = self;
}
- (void)userDidTapWebView:(id)tapPoint
{
NSLog(#"TapPoint = %f, %f", tapPoint.x, tapPoint.y);
}
Thanks, Let me know if you face any problems.
short answer: Yes, I tried something like this in the same way and it works on the real devices as well (tested with iOS 6).
ARC version of your method:
- (void) tapAtPoint:(CGPoint)point
{
Ivar internalWebViewIvar = class_getInstanceVariable([self class], "_internal");
id internalWebView = object_getIvar(self, internalWebViewIvar);
Ivar browserViewIvar = class_getInstanceVariable(object_getClass(internalWebView), "browserView");
id browserView = object_getIvar(internalWebView, browserViewIvar);
if (browserView) {
[browserView performSelector:#selector(tapInteractionWithLocation:) withObject:[NSValue valueWithCGPoint:point]];
}
}

Singleton method not getting called in Cocos2d

I am calling a Singleton method that does not get called when I try doing this.
I get no errors or anything, just that I am unable to see the CCLOG message.
Under what reasons would a compiler not give you error and not allow you to call a method?
[[GameManager sharedGameManager] openSiteWithLinkType:kLinkTypeDeveloperSite];
The method is defined as follows:
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType"); //I NEVER SEE THIS MESSAGE
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
HERE IS THE CODE TO MY SINGLETON:
#import "GameManager.h"
#import "MainMenuScene.h"
#implementation GameManager
static GameManager* _sharedGameManager = nil;
#synthesize isMusicON;
#synthesize isSoundEffectsON;
#synthesize hasPlayerDied;
+(GameManager*) sharedGameManager
{
#synchronized([GameManager class])
{
if (!_sharedGameManager)
{
[[self alloc] init];
return _sharedGameManager;
}
return nil;
}
}
+(id)alloc
{
#synchronized ([GameManager class])
{
NSAssert(_sharedGameManager == nil,#"Attempted to allocate a second instance of the Game Manager singleton");
_sharedGameManager = [super alloc];
return _sharedGameManager;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil)
{
//Game Manager initialized
CCLOG(#"Game Manager Singleton, init");
isMusicON = YES;
isSoundEffectsON = YES;
hasPlayerDied = NO;
currentScene = kNoSceneUninitialized;
}
return self;
}
-(void) runSceneWithID:(SceneTypes)sceneID
{
SceneTypes oldScene = currentScene;
currentScene = sceneID;
id sceneToRun = nil;
switch (sceneID)
{
case kMainMenuScene:
sceneToRun = [MainMenuScene node];
break;
default:
CCLOG(#"Unknown Scene ID, cannot switch scenes");
return;
break;
}
if (sceneToRun == nil)
{
//Revert back, since no new scene was found
currentScene = oldScene;
return;
}
//Menu Scenes have a value of < 100
if (sceneID < 100)
{
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad)
{
CGSize screenSize = [CCDirector sharedDirector].winSizeInPixels;
if (screenSize.width == 960.0f)
{
//iPhone 4 retina
[sceneToRun setScaleX:0.9375f];
[sceneToRun setScaleY:0.8333f];
CCLOG(#"GM: Scaling for iPhone 4 (retina)");
}
else
{
[sceneToRun setScaleX:0.4688f];
[sceneToRun setScaleY:0.4166f];
CCLOG(#"GM: Scaling for iPhone 3G or older (non-retina)");
}
}
}
if ([[CCDirector sharedDirector] runningScene] == nil)
{
[[CCDirector sharedDirector] runWithScene:sceneToRun];
}
else
{
[[CCDirector sharedDirector] replaceScene:sceneToRun];
}
}
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType");
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
-(void) test
{
CCLOG(#"this is test");
}
#end
Perhaps sharedGameManager is returning nil? This won't cause an error.
Edit: The issue is in the new code you posted:
if (!_sharedGameManager) {
[[self alloc] init];
return _sharedGameManager;
}
return nil;
If _sharedGameManager is non-nil, this will return nil. You want:
if (!_sharedGameManager) {
[[self alloc] init];
}
return _sharedGameManager;
Check this Cocoa With Love article on how to make a proper singleton.

TTModel load method not being called

Issue
Description
The following code results in load method of TTModel not being called. I've stepped it through the debugger, as well as stepping through the TTCatalog application. The only difference between the two that I can see, is that when the catalog sets it's DataSource in the createModel method of the controller, TTModel's load method gets called, whereas mine does not.
About the Code
I've commented the specific areas of code, to tell what they should be doing and what problem is happening, but I included all of it for completion's sake.
You should look at specifically
PositionsController's createModel method
PositionsList's load method
Those are the areas where the issue is, and would be the best place to start.
Code
PositionsController : TTTableViewController
- (id)initWIthNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.title = #"Positions";
self.variableHeightRows = NO;
self.navigationBarTintColor = [UIColor colorWithHexString:#"1F455E"];
}
return self;
}
//This method here should result in a call to the PositionsList load method
- (void)createModel
{
PositionsDataSource *ds = [[PositionsDataSource alloc] init];
self.dataSource = ds;
[ds release];
}
- (void)loadView
{
[super loadView];
self.view = [[[UIView alloc] initWithFrame:TTApplicationFrame()] autorelease];
self.tableView = [[[UITableView alloc] initWithFrame:TTApplicationFrame() style:UITableViewStylePlain] autorelease];
self.tableView.backgroundColor = [UIColor colorWithHexString:#"E2E7ED"];
self.tableView.separatorColor = [UIColor whiteColor];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
//self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
//Override UITableViewDelegate creation method, so we can add drag to refresh
- (id<TTTableViewDelegate>) createDelegate {
TTTableViewDragRefreshDelegate *delegate = [[TTTableViewDragRefreshDelegate alloc] initWithController:self];
return [delegate autorelease];
}
PositionsDataSource : TTListDataSource
#implementation PositionsDataSource
#synthesize positionsList = _positionsList;
-(id)init
{
if (self = [super init])
{
_positionsList = [[PositionsList alloc] init];
self.model = _positionsList;
}
return self;
}
-(void)dealloc
{
TT_RELEASE_SAFELY(_positionsList);
[super dealloc];
}
-(void)tableViewDidLoadModel:(UITableView*)tableView
{
self.items = [NSMutableArray array];
}
-(id<TTModel>)model
{
return _positionsList;
}
#end
PositionsList : NSObject <TTModel>
#implementation PositionsList
//============================================================
//NSObject
- (id)init
{
if (self = [super init])
{
_delegates = nil;
loaded = NO;
client = [[Client alloc] init];
}
return self;
}
- (void)dealloc
{
TT_RELEASE_SAFELY(_delegates);
[client dealloc];
[super dealloc];
}
//==============================================================
//TTModel
- (NSMutableArray*)delegates
{
if (!_delegates)
{
_delegates = TTCreateNonRetainingArray();
}
return _delegates;
}
-(BOOL)isLoadingMore
{
return NO;
}
-(BOOL)isOutdated
{
return NO;
}
-(BOOL)isLoaded
{
return loaded;
}
-(BOOL)isEmpty
{
//return !_positions.count;
return NO;
}
-(BOOL)isLoading
{
return YES;
}
-(void)cancel
{
}
//This method is never called, why is that?
-(void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more
{
//This method is not getting called
//When the PositionsController calls self.datasource, load should be called,
//however it isn't.
[_delegates perform:#selector(modelDidStartLoad:) withObject:self];
[client writeToServer:self dataToSend:#""];
}
-(void)invalidate:(BOOL)erase
{
}
#end
Short answer: return NO instead of YES for isLoading in your PositionList.
For a longer explanation:
If you dig through the Three20 source, you'll find that setting the dataSource on a view controller sets the model, refreshing the model and potentially calling load. Here is the code called when the TTModelViewController refreshes:
- (void)refresh {
_flags.isViewInvalid = YES;
_flags.isModelDidRefreshInvalid = YES;
BOOL loading = self.model.isLoading;
BOOL loaded = self.model.isLoaded;
if (!loading && !loaded && [self shouldLoad]) {
[self.model load:TTURLRequestCachePolicyDefault more:NO];
} else if (!loading && loaded && [self shouldReload]) {
[self.model load:TTURLRequestCachePolicyNetwork more:NO];
} else if (!loading && [self shouldLoadMore]) {
[self.model load:TTURLRequestCachePolicyDefault more:YES];
} else {
_flags.isModelDidLoadInvalid = YES;
if (_isViewAppearing) {
[self updateView];
}
}
}
Your PositionList object is returning YES for isLoading and NO for isLoaded. This means that Three20 thinks your model is loading when it isn't. You may also need to implement shouldLoad if it doesn't return YES by default.