Why is my outlet nil? - objective-c

I'm having trouble with a cocoa project. I'm displaying a keyboard composed of NSButtons, and I'd like that when I click on one of the keys, the label is added to a NSTextField. I have a controller that I use as a singleton, so each key "knows" how to access the controller. In the controller, I have an outlet linked to the NSTextField. When I click on a key, nothing happens. So I used something like NSLog(#"%#", [[[OakController] sharedInstance] textarea]) on a mouseDown event, and in the console output, I get (null).
Long story short, my outlet is set to nil, and I don't know why it is that way, or how to solve that...
Here's the code of the controller : https://gist.github.com/1090564. Sorry for the lack of syntax coloring.
Thanks for reading guys!

My guess is that you actually have multiple instances of OakController instead of one like you expect. Did you drag a blue cube into your IB document and change its custom class to OakController? That will allocate and initialize a new object each time. I'd guess that your sharedInstance method also allocates and initializes an instance.
Try adding an awakeFromNib method to OakController, and add a break point. Log self's pointer value. In your second case were the outlet is unexpectedly nil, also log self's pointer address.

Related

Updating a UILabel from another view using NSNotificationCenter

I'm updating a UILabel in another view using a method invoked using the NSNotification center.
What I'm finding is that it's not working, the label remains unchanged until the view is reloaded at a later point (not using the method called by nsnotification center).
The label is set to nonatomic, retain and I'm using self to bind everything to that property.
obviously all of the code (the method) that tries to update the label is located in the corresponding viewController. The method gets called (I can see that with NSLog), and all it does is self.label2.text = #"label 2 updated";
I'm able to update the text value of the label using a timer just fine, so I don't think there is a problem with the label, but rather with my understanding of threading and/or what is loaded or not at a given time.
What do you think is the problem?
PS: i WILL chose an answer even if it doesn't help me that much, so please post your wildest guesses!
First make sure that your label is not nil when you set the text.
If it's not nil, try calling setNeedsLayout on your label after setting the text (this shouldn't be necessary). Also, verify that you are on the main thread with:
[NSThread isMainThread]
If that doesn't help, use a subclass of UILabel, override setText and put a breakpoint on it to see what's going on.

Prevent instance variables from being cleared on memory warning

I've got a relatively simple problem that's been evading solution for some time. I have a view controller and an associated XIB. The view controller is called FooterViewController. FooterViewController's view is set as the footer view of a tableview.
FooterViewController's view includes a label for showing feedback to the user. I would like this label to persist until its value is changed by my application. Under normal circumstances, it does. However, I've just begun testing with memory warnings, and I've found that after the view is unloaded in response to a memory warning, the label is cleared.
Here's what I've tried so far to solve the problem: in FooterViewController's viewWillUnload method, I store the label's text in an instance variable called statusString:
- (void)viewWillUnload
{
statusString = [statusLabel text];
testInt = 5;
NSLog(#"View will unload; status string = %#; testInt = %d",
statusString, testInt);
[super viewWillUnload];
}
Note that I've also set another instance variable, declared as NSInteger testInt, to 5.
Then, in FooterViewController's viewDidLoad method, I try to set the label's text to statusString:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Just before setting label, statusString: %#; testInt: %d",
statusString, testInt);
[statusLabel setText:statusString];
NSLog(#"View did load.");
}
However, this does not work. Further, in the log after simulating a memory warning, I see:
View will unload; status string = Invalid IP address Error code: 113; testInt = 5
(Note that "Invalid IP address Error code: 113" is the correct value for statusString)
Then, after navigating to FooterViewController again, I see:
Just before setting label, statusString: (null); testInt: 0
This indicates to me that for some reason, the instance variables of FooterViewController are being reinitialized when the view loads again. A final note: the method initWithNibName:bundle: is being called each time the view must reload, though I expect this; after all, the view must be reloaded from the NIB.
So, my questions are these:
Why do these instance variables appear to be nullified or zeroed in the process of unloading and reloading the view?
If I'm doing something incorrectly that's causing this nullification, what is it?
If I'm not doing anything incorrectly, and this is normal behavior, how should I handle maintaining state between loads of the view?
Thanks,
Riley
The statusString looks like a weak reference, not a strong property. It can not retain the labels's text, which gets deallocated with the label when the view is unloaded. That's why you get first a correct value (before the label is deallocated), and null later (after the label has been deallocated, and the weak ref nullified). Turn your statusString into a strong property, and that ARC magic won't bite you any longer.
It looks like you need to be using didRecieveMemoryWarning instead of viewDidUnload, since viewDidUnload is not guaranteed to be called in the event of a memory warning. If the crash is exiting the app completely then you need to be writing the data to disk using something like coreData. Save your data here and then call the super so the view will still be released. Hope that helps.
I figured out what was going on, finally. The issue was that I called the allocation and initialization methods for FooterViewController in its parent view controller's viewDidLoad method. When the views were dumped and subsequently reloaded, my view controller was re-initialized! This destroyed the original FooterViewController, which maintained the instance variables I needed, and replaced it with a brand-new VC.
The solution was to move [[FooterViewController alloc] init] to the init method of FooterViewController's parent VC, so that the initialization was only performed once per run cycle.
I've learned my lesson: don't reinitialize your view controllers unless you really mean to do so. As such, be very careful where you put your calls to the initializer in parent view controllers.
Thanks for the help I got from the two answerers.

Cannot set an NSWindow's position

I have a class that extends NSWindowController and I am trying to position the window it controls. The window displays all of the expected contents and functions correctly, but when I try and position its starting location on the screen in the initWithWindowNibName method, the position does not change. Here is the code:
NSPoint p = NSMakePoint(100, 50);
[[self window] setFrameTopLeftPoint:p];
This seems very straight forward and I'm not sure what the problem is.
Thanks for any ideas.
(Found the problem. I did not have the window wired up to the Class in IB.)
Wevah has the right idea, though I'll try to expand on it a bit.
If you were to try adding this line to your initWithWindowNibName: method:
NSLog(#"window == %#", [self window]);
You would likely see the following output to console:
window == (null)
In other words, the window is still nil, as init* methods are so early on in an object's lifetime that many IBOutlets or user interface items aren't quite "hooked up" yet.
Sending a message to nil is perfectly fine: it's simply ignored. So, basically your attempt to position the window has no effect because it basically equates to [nil doSomething];
The key then is to perform the positioning of the window later on in the controller object's lifetime, where the IBOutlets and other user interface objects are properly hooked up. As Wevah alluded to, one such method where things are properly hooked up is
- (void)awakeFromNib;
or in the case of NSWindowController, the following one as well:
- (void)windowDidLoad;
Hope this helps...
Try putting that code in awakeFromNib.

NSCFString or UIViewController?

I am using UIViewController (a subclass of course) with a text field which sends an action when the contents changed (to the contentsChanged: selector of the ViewController). It is done by sending contentsChanged: to file's owner in IB.
But when I test it, it says : "-[NSCFString contentsChanged:] : unrecognised selector sent to instance " and the instance pointer in hex.
I am guessing that for some reason the view controller gets moved to another pointer and a string gets allocated there, but I cannot figure why.
Any ideas ?
Sounds like a classic case. Read up on NSZombieEnabled for how to track this sort of problem down.
I have the exact same problem with a subclass of UIViewController and this piece of innocuous code:
- (void)viewDidLoad
{
NSLog(#"%# %s %#", [self class], _cmd, answerButton);
[self.answerButton addTarget:self
action:#selector(getAnswerToQuestion:)
forControlEvents:UIControlEventTouchUpInside];
}
Yes, answerButton is connected (it's an IBOutlet), yes, - (IBAction)getAnswerToQuestion:(id)sender; is a proper method, but no joy. When I commented out the viewDidLoad and made the connection in IB, it showed in the crash report that the failure happens on [UIControl sendAction:to:forEvent:] resulting in
objc_msgSend() selector name: performSelector:withObject:withObject:
I can't prove it, but I suspect there's a bug somewhere in the UIKit that translates the bindings and addTarget to a call to performSelector. I'm planning to upgrade to iOS 4.01 first to see if that won't solve the problem.
UPDATE:
I'm not sure anymore that my problem really is similar to Alexandre Cassagne's but in the interest of sharing information I will not delete it just yet. I solved my problem, as so often, when I started to make an example project in order to file a bug report. Yes, clicking made answerButton call getAnswerToQuestion: like a good little object and all was fine.
The difference between the subclassed UIViewController of the example project and that of my real project was that the first also functioned as the xib's File's Owner while the second was just one of several view controller. When I moved getAnswerToQuestion: to the File's Owner in my real project, clicking answerButton worked as expected. So, my hunch that the problem lay somewhere in the translation from binding to performSelector wasn't that far off: the problem lies in the Responder Chain. I would think that establishing the Action-Target link either programmatically or in IB would bypass the Responder Chain, but apparently not.
The problem now, of course, is that Alexandre states in his question that his contentsChanged: method already is part of the File's Owner, which makes my answer irrelevant to the question.
without looking at the code, it looks like you are calling contentsChanged: on the text field's text, instead of the UIViewController subclass.
you should consider using the UITextFieldDelegate protocol to get called back when the text of a UITextField changes. I have not looked, but this is the thing I would do off the top of my head.

How does NSViewController avoid bindings memory leak? [have sample app]

I'm trying to implement my own version of NSViewController (for backwards compatibility), and I've hit a problem with bindings: Since bindings retain their target, I have a retain circle whenever I bind through File's owner.
So I thought I'd just explicitly remove my view from its superview and release the top level objects, and that would take care of the bindings, because my controller isn't holding on to the views anymore, so they release me and I can go away. But for some reason, my view controller still doesn't get released. Here's a sample app exhibiting the problem:
http://dl.dropbox.com/u/34351/BindingsLeak.zip
Build it, launch it, and hit Cmd-K ("Create Nib" in "Edit" menu) to load a NIB into the empty window. Hit Cmd-K again to release the first view controller (TestNibOwner) and load a new one. The old view controller never gets dealloced, though.
Remove the "value" binding on the checkbox, and it gets released just fine.
If you set breakpoints at the release/retain/autorelease overrides, you see that _NSBindingInfo retains the TestNibOwner, but never releases it in the leaking case.
Anyone know how to fix this?
Doing a little investigation with class-dump and friends, it looks like Apple has a private class called NSAutounbinder that takes care of this dirty work for classes such as NSViewController and NSWindowController. Can't really tell how it works or how to replicate it though.
So, I can't really answer your question on how to prevent the retain cycle from happening for arbitrary bindings in a loaded nib, but perhaps it's some consolation to know that Apple is cheating, and you're not missing anything obvious. :-)
One thing I've done for the same problem is to create a proxy NSObjectController inside my nib. My NSViewController-like class has a pointer to this proxy and all bindings are bound through it. When I want to cleanup the view controller, I then do [selfProxy setContent:nil] on the object controller and release the view controller. In this instance the NSObjectController proxy acts as the auto-unbinder in this case.
It's more manual and you can't just release the view by itself, but it does solve the retain problem.
I'd suggest you do this:
-(void) releaseTopLevelObjects
{
// Unbind the object controller's content by setting it to nil.
[selfProxy setContent:nil];
NSLog( #"topLevelObjects = %#", topLevelObjects );
[topLevelObjects release];
topLevelObjects = nil;
}
In your nib, bindings would happen through a path like:
selfProxy.content.representedObject.fooValue
When you remove your view from its superview, are you also sending it another -release message? It was created by unarchiving from the nib, right?