TTModel load method not being called - cocoa-touch

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.

Related

Animating custom property

I'm trying to animate a custom property, and as I've seen on several sources it seems this would be the way to go, but I'm missing something. Here's the CALayer subclass:
#implementation HyNavigationLineLayer
#dynamic offset;
- (instancetype)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
if (self) {
HyNavigationLineLayer * other = (HyNavigationLineLayer*)layer;
self.offset = other.offset;
}
return self;
}
-(CABasicAnimation *)makeAnimationForKey:(NSString *)key
{
// TODO
return nil;
}
- (id<CAAction>)actionForKey:(NSString *)event
{
if ([event isEqualToString:#"offset"]) {
return [self makeAnimationForKey:event];
}
return [super actionForKey:event];
}
+ (BOOL)needsDisplayForKey:(NSString *)key
{
if ([key isEqualToString:#"offset"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
- (void)drawInContext:(CGContextRef)ctx
{
NSLog(#"Never gets called");
}
#end
I believe this is the only relevant method on my view:
#implementation HyNavigationLineView
+ (Class)layerClass
{
return [HyNavigationLineLayer class];
}
#end
And, finally, in my view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate the navigation line view
CGRect navLineFrame = CGRectMake(0.0f, 120.0f, self.view.frame.size.width, 15.0f);
self.navigationLineView = [[HyNavigationLineView alloc] initWithFrame:navLineFrame];
// Make it's background transparent
self.navigationLineView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
self.navigationLineView.opaque = NO;
[[self.navigationLineView layer] addSublayer:[[HyNavigationLineLayer alloc] init]];
[self.view addSubview:self.navigationLineView];
}
The thing is that the drawInContext method is not called at all, although layerClass is. Am I missing something to make the layer draw?
Solved it. Need to call setNeedsDisplay
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate the navigation line view
CGRect navLineFrame = CGRectMake(0.0f, 120.0f, self.view.frame.size.width, 15.0f);
self.navigationLineView = [[HyNavigationLineView alloc] initWithFrame:navLineFrame];
// Make it's background transparent
self.navigationLineView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
self.navigationLineView.opaque = NO;
[[self.navigationLineView layer] addSublayer:[[HyNavigationLineLayer alloc] init]];
[self.view addSubview:self.navigationLineView];
[self.navigationLineView.layer setNeedsDisplay];
}

Can I initiate an ivar indirectly?

I'm trying to initiate my ivar like this:
Declared like this in h-file
#interface MyClass: {
UITextView *_myTextView;
}
then created like this in the m-file
- (id)init {
self = [super init];
if(self) {
[self initTextView:_myTextView];
}
return self;
}
- (void)initTextView:(UITextView *)textView {
textView = [[UITextView alloc] init];
...
}
_myTextView will still be nil afterwards. Why is that and what should I do it to make it work? I've got ARC enabled.
[EDIT]
This works. Thanks all!
- (id)init {
self = [super init];
if (self) {
_textView1 = [self createTextView];
_textView2 = [self createTextView];
_textView3 = [self createTextView];
}
return self;
}
- (UITextView *)createTextView {
UITextView *textView = [[UITextView alloc] init];
...
return textView;
}
You need to always refer to instance variables using:
self.textView = [[UITextView alloc] init];
Also use a name other than initTextView as methods starting with init have special meaning in Objective-C.
If you want to use the same code to initialize multiple text view controls, then use code like this:
- (UITextView *)createTextView
{
UITextView *textView = [[UITextView alloc] init];
textView.something = whatever;
...
return textView;
}
And then use it like this:
- (id)init {
self = [super init];
if(self)
{
self.textView1 = [self createTextView];
self.textView2 = [self createTextView];
...
self.textViewN = [self createTextView];
}
}
In [self initTextView:_myTextView]; you pass the current value of _myTextView (which is nil) to your initTextView: method. To set the instance variable, you need a pointer to a pointer.
- (id)init {
self = [super init];
if (self) {
[self setupTextView:&_myTextView];
}
return self;
}
- (void)setupTextView:(UITextView * __strong *)textView {
*textView = [[UITextView alloc] init];
...
}
I also renamed the initTextView: method to setupTextView, as methods starting with init are expected to behave like other init methods in ARC.
- (id)init {
self = [super init];
if(self) {
[self initTextView];
}
}
- (void)initTextView{
_myTextView = [[UITextView alloc] init];
...
}
if you want to call initTextView for several text views , you can code like this :
- (id)init {
self = [super init];
if(self) {
_myTextView = [[UITextView alloc] init];
[self initTextView:_myTextView];
}
}
- (void)initTextView:(UITextView *)textView{
//setup the textView
...
}

Custom field editor not drawing

I'm trying to subclass NSTokenField to intercept some of the keyboard events. I wrote subclasses for NSTokenField, NSTokenFieldCell and NSTextView. In the NSTokenField subclass I swap the regular cell with my custom cell and in the custom cell I override -(NSTextView*)fieldEditorForView:(NSView *)aControlView to provide my textview as a custom field editor. All the initialisation methods are called as expected but for some reason my custom token field is not drawn.
Here is the code for the NSTokenField subclass:
#synthesize fieldEditor = _fieldEditor;
-(JSTextView *)fieldEditor
{
if (!_fieldEditor) {
_fieldEditor = [[JSTextView alloc] init];
[_fieldEditor setFieldEditor:YES];
}
return _fieldEditor;
}
- (void)awakeFromNib {
JSTokenFieldCell *newCell = [[JSTokenFieldCell alloc] init];
[self setCell:newCell];
}
+ (Class) cellClass
{
return [JSTokenFieldCell class];
}
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self) {
JSTokenFieldCell *newCell = [[JSTokenFieldCell alloc] init];
[self setCell:newCell];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
JSTokenFieldCell *newCell = [[JSTokenFieldCell alloc] initWithCoder:aDecoder];
[self setCell:newCell];
}
return self;
}
And here is the code for the subclass of NSTokenFieldCell:
-(NSTextView*)fieldEditorForView:(NSView *)aControlView
{
if ([aControlView isKindOfClass:[JSTokenField class]]) {
JSTokenField *tokenField = (JSTokenField *)aControlView;
return tokenField.fieldEditor;
}
return nil;
}
- (id)initWithCoder:(NSCoder *)decoder
{
return [super initWithCoder:decoder];
}
- (id)initTextCell:(NSString *)aString
{
return [super initTextCell:aString];
}
- (id)initImageCell:(NSImage *)anImage
{
return [super initImageCell:anImage];
}
Addition
After further digging I found this post which says that the only way to have an NSTokenField with a custom text view is by overriding private methods. Is it true? If so, is there any other way I can intercept keyboard events without subclassing NSTextView?

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.

Accessing Singleton object second times caused app to crash

I have a a singleton class here is the code
#import <Foundation/Foundation.h>
#import "CustomColor.h"
#interface Properties : NSObject {
UIColor *bgColor;
CustomColor *bggColor;
}
#property(retain) UIColor *bgColor;
#property (retain) CustomColor *bggColor;
+ (id)sharedProperties;
#end
#import "Properties.h"
static Properties *sharedMyProperties = nil;
#implementation Properties
#synthesize bgColor;
#synthesize bggColor;
#pragma mark Singleton Methods
+ (id)sharedProperties
{
#synchronized(self)
{
if(sharedMyProperties == nil)
[[self alloc] init];
}
return sharedMyProperties;
}
+ (id)allocWithZone:(NSZone *)zone
{
#synchronized(self)
{
if(sharedMyProperties == nil)
{
sharedMyProperties = [super allocWithZone:zone];
return sharedMyProperties;
}
}
return nil;
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (void)release {
// never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init])
{
bgColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:1.0];
FSColor *bc = [[FSColor alloc] init];
bc.red = bc.green = bc.blue = bc.hue = bc.sat = bc.bri = 0;
bggColor = bc;
}
return self;
}
- (void)dealloc
{
// Should never be called, but just here for clarity really.
[bgColor release];
[bggColor release];
[super dealloc];
}
#end
I have a UIView' subclass. in which i am using it. I am calling drawRect method after each second. It only run once and then app crashes.
- (void)drawRect:(CGRect)rect
{
Properties *sharedProprties = [Properties sharedProperties];
…...
….
CGContextSetFillColorWithColor(context, [[sharedProprties bgColor] CGColor]);
…..
}
What if you do self.bgColor = [UIColor colorWithRed:...]?
Without the self. I think you may be accessing the ivar directly, and therefore assigning it an autoreleased object which won't live very long (rather than using the synthesized property setter, which would retain it). I could be wrong about this, I'm stuck targeting older Mac OS X systems so I haven't been able to play much with Objective-C 2.0.
Your static sharedProperties gets never assigned. It is missing in the init method or in the sharedProperties static method.
Here's a sample pattern for singletons (last post)
Also accessing properties without self. may cause bad access errors too.
Regards
Not an answer, but note that a simple call to [Properties alloc] will mean it's no longer a singleton!
+ (id)sharedProperties
{
#synchronized(self)
{
if(sharedMyProperties == nil)
{
sharedMyProperties = [[self alloc] init];
}
}
return sharedMyProperties;
}