I have been seeing some strange behavior when I try to access a class variable or a property in my drawRect method..
In my .h file I have the following
#interface DartBoard : UIView
{
Board * board;
int index;
}
#property (readwrite, assign, nonatomic) NSNumber * selectedIndex;
#end
In my .m file I have the following
#implementation DartBoard
#synthesize selectedIndex;
-(id)init
{
self.selectedIndex = [NSNumber numberWithInt:5];
index = 123;
return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"selectedIndex: %d",[self.selectedIndex intValue]);
NSLog(#"index: %d",index);
}
#end
the output is
2012-06-12 19:48:42.579 App [3690:707] selectedIndex: 0
2012-06-12 19:48:42.580 App [3690:707] index: 0
I have been trying to find a solution but have had no luck..
I found a similar question but there was no real answer to the issue
See: UIView drawRect; class variables out of scope
I have a feeling drawRect is different that normal methods and is not getting the scope of the class correctly but how do I fix it?
Cheers
Damien
I have a feeling drawRect is different that normal methods and is not getting the scope of the class correctly
No, there is nothing special about -drawRect:.
There are two possibilities:
1. Your -init method is not being called.
You didn't say how this view gets created -- if you are manually calling [[DartBoard alloc] init], or if it is getting unarchived from a nib file.
If it's coming from a nib, UIView's unarchiving doesn't know that your init method should be called. It will call the designated initializer instead, which is -initWithFrame:.
So, you should implement that method instead, and make sure to call super!
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.selectedIndex = [NSNumber numberWithInt:5];
index = 123;
}
return self;
}
2. There might be two instances of your view: one that you are manually initing, and another one that comes from somewhere else, probably a nib. The second instance is the one that is being drawn. Since its variables and properties are never set, they show up as zero (the default value).
You could add this line to both your -init and -drawRect: methods, to see what the value of self is. (Or, check it using the debugger.)
NSLog(#"self is %p", self);
Related
This is something very fascinating I observed today. Maybe that's how Objective-C works but I didn't know about this. See the following code below:
// ATableViewController.h
#interface ATableViewController : UITableViewController
#end
// ATableViewController.m
#interface ATableViewController ()
#property (nonatomic) int volly;
#end
#implementation ATableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.volly = 5;
}
#end
// BTableViewController.h
#interface BTableViewController : ATableViewController
#end
// BTableViewController.m
#interface BTableViewController ()
#property (nonatomic) int volly;
#end
#implementation BTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%d", self.volly); // returns 5
}
#end
I am not sure why the above is valid. I do understand that I passed a message 'volly' to the object 'self' which in turn probably looked at the value from the super class but shouldn't these be initialized? Some explanation would be of great help. Thanks.
EDIT: This is a big problem IMO though. Considering I don't know any of the private properties defined in the super class, my own set of values might end up being different.
For example, a developer may set a boolean flag hasAppeared in viewDidAppear:. This same value will be set for my subclass instance in viewDidAppear: after the [super viewDidAppear:] call. This will be before I actually get to set it myself.
Currently, the solution is I know exactly the variable used by the super class and I can avoid using the same value but I deem this to be a larger issue than it seems.
EDIT 2: The behavior is consistent with binaries (with only headers) as well as with frameworks where implementation is available.
I am answering this without reading all of the comments.
There is no issue here. Both AViewController (AVC) and BViewController (BVC) each have their own private property named volly.
You created an instance of BVC. It can't see the volly property from its parent class (because it is private), just its own.
Now the fun begins.
The viewDidLoad method from BVC is called. It in turn calls [super viewDidLoad]; which of course calls the viewDidLoad from the AVC class. That method, in turn, calls self.volly = 5;.
The confusion seems to be with this line. Remember, self.volly = 5; is really a call to:
[self setVolly:5];
Both AVC and BVC have the (synthesized) setVolly: method. Since self is a pointer to an instance of a BVC object, the call to [self setVolly:5]; results in a call to the setVolly: method in the BVC class despite being called from a method in the AVC class.
Here's the code with some annotations:
The 'BVC' class:
- (void)viewDidLoad
{
[super viewDidLoad]; // calls the method in `AVC`
NSLog(#"%d", self.volly); // returns 5
}
The 'AVC' class:
- (void)viewDidLoad
{
[super viewDidLoad]; // calls the UITableViewController method
// The following is really [self setVolly:5];
// Since "self" is a "BVC", the private "volly" property of
// the "BVC" class is actually set here.
// The private "volly" property of the "AVC" class will still be
// 0 after this call.
self.volly = 5;
}
In the end, the subclass is not using the private property of the parent class. The original premise in the question's title is incorrect.
I am trying to run a non-void function returning an NSArray, but when I run it, there's not even a log line:
- (NSArray *) arrayFunction
{
return myList;
}
This is how I call the function:
- (void) myMainFunction
{
[self arrayFunction];
}
I also tried with NSLog and a void function instead of NSArray, but that won't show up either.
It is a NSView class.
Thanks for you help!
*EDIT: * Full Code:
Implementation file:
#import "LogNavigator.h"
#implementation LogNavigator
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
[self myMainFunction];
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
- (NSArray *) arrayFunction
{
// # Get the list of .txt files, this part works correctly as expected in CodeRunner
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:#"Desktop"];
NSArray *directoryList = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]
pathsMatchingExtensions:[NSArray arrayWithObjects:#"txt", nil]];
return directoryList;
}
- (void) myMainFunction
{
[self arrayFunction];
}
#end
If this custom NSView is created using Interface Builder then you should override awakeFromNib:
- (void)awakeFromNib {
[self myMainFunction];
}
Note, however, that you are ignoring the return from arrayFunction, so it's feasible the compiler might omit the call entirely during an optmimized release build if it can determine no side effects of the call.
EDIT: Note that you need to set the NSView-derived class within the view of the window within MainMenu.xib, using IB, in order for this method to be triggered when the view is loaded.
When objects are loaded from a nib (or a Storyboard) The normal initialiser is not called, instead initWithCoder is called.
When setting up objects from a nib, you should override initWithCoder and put your initialiser code in there instead.
If you need to access or set up other objects in the nib as part of initialisation, then awakeFromNib is a better method to override, because it is called after all the other nib objects have been loaded.
edit
When you added the view object to the nib, did you specify it's class as LogNavigator? This is something that people frequently forget to do.
In my program, I am assigning a value to a property in the init method. I later use this property in response to an event.
Given a property named Object; why does the synthesized setter [self setObject:obj] work, but Object = obj give an Invalid Selector exception? Is it because the mutator adds one to the reference count on obj?
Edit: Here is more code, to give context. When I say "work" above, I mean run without errors.
Here is the initialization of the object(A View Controller) which has the property in it:
Note: All properties are declared as (nonatomic, retain).
#synthesize _Kiosk;
....
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andKiosk: (Kiosk*) kiosk
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self set_Kiosk:kiosk]; //This work witout error. _kiosk = kiosk Fails at point below.
}
return self;
}
....
- (IBAction) ActionPressed:(id)sender
{
[_CompanyName setText: [_Kiosk _CompanyName]]; //Failure happens here
}
And here is the method call that initializes this View Controller:
#synthesize _Kiosk;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[[UIApplication sharedApplication] setStatusBarHidden:true withAnimation:UIStatusBarAnimationFade];
_Kiosk = [[Kiosk alloc] init];
self.window.rootViewController = [_KioskViewController initWithNibName:#"iPadKioskView" bundle:nil andKiosk: _Kiosk];
[_Kiosk release];
[self.window makeKeyAndVisible];
return YES;
}
#property basically just declares two methods: setFoo: and foo. #synthesize will create the method bodies for those methods, which will correctly retain the new value and release the old one.
Assigning directly to the instance variable, however, doesn't call those accessor methods. It just makes the pointer point to a new object. If you're going to manually set the instance variable, then you need to make sure to correctly retain new value and release the old one.
Note that [self setFoo:someFoo] and self.foo = someFoo will both call the accessor method, while foo = someFoo just sets the instance variable directly, bypassing your setter method.
Your posted code gave the answer. You set _Kiosk already, so don't release it. There is no need to assign _Kiosk in the initWithNibName:etc. anymore. Just release the Kiosk in the dealloc method. If you want to change the Kiosk, use class.Kiosk = so the property is invoked, which is bound to retain it and release the previous kiosk.
You definitively have to retain your kiosk parameter in the [init..] method. Otherwise it will be released, and you get an error soon or later.
One way is to do it yourself by calling retain on it, or you can use a synthetized setter, if it is defined as (nonatomic, retain).
I have a problem with the init() method of a standard NSObject. I wrote a class (EFAPersistence) which is a subclass of NSObject. EFAPersistance has a attribute called efaDatabase.
EFAPersistence.h
#interface EFAPersistence : NSObject {
FMDatabase * efaDatabase;
}
#property (assign) FMDatabase * efaDatabase;
Everytime an instance of EFAPersistance is created I want to assign efaDatabase a value from my AppDelegate.
EFAPersistence.m
#implementation EFAPersistence
#synthesize efaDatabase;
- (id)init {
if (self = [super init]) {
efaDatabase = [[NSApp delegate] efaDatabase];
}
return self;
}
#end
This way of assigning does not work. But it works if I put the code in a normal method. So I am sure that efaDatabase is correctly instantiated in my AppDelegate. It's just not working in my init() method. That's why I have the feeling that NSApp is not working inside the init() method.
That's how the important AppDelegate code looks like.
AppDelegate.h
#interface AppDelegate : NSObject <NSApplicationDelegate> {
FMDatabase * efaDatabase;
}
AppDelegate.m
- (id)init {
if (self = [super init]) {
NSString * databasePath =
[[NSBundle mainBundle] pathForResource:#"efa" ofType:#"sqlite"];
self.efaDatabase = [FMDatabase databaseWithPath:databasePath];
if (![efaDatabase open]) {
NSLog(#"Couldn't open database: %#", databasePath);
// TODO: Create a database here
}
self.db = [[EFAPersistence alloc] init];
}
return self;
}
As you can see I am calling the init method. I also affirmed this by using NSLog(). init() is called. The attribute I am trying to assign in EFAPersistence is also created before init() is called.
To sum everything up:
How can I make this work within the init() method so I do not have to write boiler plate code in all my methods of EFAPersistence?
It looks to me that your AppDelegate is unset when you try to create the EFAPersistance object the first time. This is on below line in AppDelegate.m
self.db = [[EFAPersistence alloc] init];
I imagine the app delegate is set after the init is done (returned).
This way of assigning does not work. But it works if I put the code in a normal method. So I am sure that efaDatabase is correctly instantiated in my AppDelegate. It's just not working in my init() method. That's why I have the feeling that NSApp is not working inside the init() method.
NSApp works fine.
Quoting epatel:
I imagine the app delegate is set after the init is done (returned).
Correct. The nib loader completely instantiates each object (including the app delegate, if it's in a nib), then sets it as the value of any properties it's connected to. These are two separate operations; it will not set a not-yet-initialized object as the application delegate.
Quoting you (Jens) again:
The question is how to assign efaDatabase in EFAPersistences only once . There are other methods like awakeFromNib and viewDidLoad etc. But those are not available in a plain NSObject subclass.
Incorrect. awakeFromNib is sent to every object in a nib after the object has been initialized.
That said, I'm curious as to why you have EFAPersistence in a nib. From its name, it doesn't sound interface-related. Shouldn't the app delegate own the EFAPersistence, and the EFAPersistence own the database directly?
I have some class initialized in Appdelegate, but when I get this class instance form Appdelegate in another class it has "fresh" state.
I have following in AppDelegate:
Interface:
#property (nonatomic, retain) DataController *dataController;
Implementation:
#synthesize dataController;
- (id)init {
if (self = [super init]) {
DataController *controller = [[DataController alloc] init];
self.dataController = controller;
[controller release];
NSLog(#"items: %d",[self.dataController numberOfItems]);
}
return self;
}
At this point DataControlelr class loads objects form database. Log output show "items: 10".
I have TableViewController where I need to use DataController.
TableViewController header:
#interface TableViewController : UITableViewController {
DataController *dataController;
}
#property (retain) DataController *dataController;
#end
Implementation:
-(id)init{
if (self =[super init]) {
DataController *dc =[(AppDelegate *)[[UIApplication sharedApplication] delegate] dataController];
[dc retain];
dataController = dc;
NSLog(#"items: %d",[self.dataController numberOfItems]);
}
return self;
}
Here it always says that DataController has 0 items. "fresh" state.
The Log output is always
items: 10
items: 0
It seems like assigning that class creates reference to freshly initialised DataController somehow?
How do I reference another class properly?
Thanks.
The first thing to check would be to ensure that the dc variable in the second class isn't nil-- that would cause any method called on it to 'return' 0.
It might also be useful to print out the address of the app delegate from both of those methods-- just in case the -init method is resulting from an incorrectly-allocated second instance of that class somewhere, while the regular version hasn't been initialized in the same way (or was using -initWithCoder:, etc.)
One useful rule of thumb for initialization of objects created or assigned within a nib file is to use -awakeFromNib to perform most of your initialization tasks. A corollary to this is that the app delegate can set up its state in response to the -applicationDidFinishLaunching: method. In this case, if there is a second instance of your AppDelegate class being allocated somewhere, only the one which is really set as the app's delegate will receive -applicationDidFinishLaunching:.
At the end of the day, stepping through in the debugger and looking at the call stack should show you if something isn't happening in quite the way it should.
Could there be an issue with your assignment of dataController = dc in TableViewController? In your log statement you use self.dataController, should your assignment directly above it be self.dataController = dc ?
I found the gotcha. Tanks to Jim!
Moved assignment from -init to -awakefromnib and now DataController is valid.
My mistake is that after putting the code initially in -viewDidLoad and -viewWillAppear which was wrong I thought that in -init is the place for the assignment.