Need some clarity on NIB file objects - objective-c

I have a MainViewController class set up with a simple nib. On the nib i have only these few objects. I have set the popoverViewController's file owner to the MainViewController.
I have set the popoverViewController class to my PVController class so that i can put buttons/code/labels etc.
The Popover works fine, but somethings are is puzzling me which i need help to understand.
That newly created PVController class has an init method that is never called when the popover is loaded. Yet the popover still works? If i put break points in the -(IBAction) buttons however, they are called when i click the buttons for them. But the init is never called. If I however, go to the file owner (MainViewController) and do --> PVController *pv = [[PVController alloc] init], only then it is called. So my first question is, will there be any problems if i do not alloc/init an IBOutlet in FileOwner, since it seems to work without it (altho the PVController's init method isnt called)? And why isnt it called?
If i did decide to create that IBOutlet in FileOwner for the Popover View Controller's referencing outlet, do i make it as strong? or weak? My noobish instincts tells me weak because it is already owned by the NIB, but when i put weak, I get a yellow error next to the init of the IBOutlet saying message

So my first question is, will there be any problems if i do not alloc/init an IBOutlet in FileOwner
NO, there will not be any problem. Infact IBOutlets are never alloc+init manually.

Related

CoreData, NSArraycontroler and NSTable, NSTable does not update after object added

Still learning so I could really use some help from you more seasoned folks. I'm learning more about the MVC model and trying to implement this with CoreData. My test application has the AppDeligate, MainController and a MocManager. All CoreData stuff is moved to the MocManager.
On the xib I have an NSTable, an NSArrayController and a NSButton.
In MainController, I have an instance of the MocController with a simple method to add a name to the database.
-(IBAction) addPerson:(id)sender{ [manager addperson:#"Sam"]; }
This method is called when the NSButton is pressed. Pressing the NSButton does add the name to the database, however it does not update the table. If I close the App and reopen, the name will appear in the NSTable, so the adding method is good and is working.
I tried both of the following (Separately):
Linked the NSArrayController to MainController through an IBOutlet
Linking the NSArrayController to MocManager through an IBOutlet
In both cases, I used [ArrayController rearrangeObjects] method after the save - the NSTable still does not update.
--[EDIT]----
Appears to be a bindings issues as pointed out by Vadian - but I cannot seem to figure out where/why.
In a previous App I successfully moved the coredata stack in to the MainController, all bindings appear to be working properly.
For this test App, I moved the coredata stack to it's own controller - "MocManager".
In MainController I created an instance of MocManager with:
MocManager* Manager; //This is in .h
manager = [[MocManager alloc] init]; //This is in .m init method
Data loads in to table on startup, but will not sync when using addPerson method in MainController.
Also, I connected an NSButton directly to the remove method on the NSArrayController - the items are removed from the NSTable but not saved, they reappear when App restarted. This all further points to the bindings issue.
Any pointers/thoughts/help would be greatly appreciated.
--[EDIT - SOLUTION]-----
Thanks to Vadian for pointing me in the right direction, it was in fact my bindings.
In the NSArrayController in the XIB I needed to bind it properly to the correct ManagedObjectContext. In my case it is to manager.ManagedObjectContext.
Hope this helps someone.

Reasons for an IBOutlet to be nil

What are the reasons why an IBOutlet (connected) could be nil?
I have one in may application which is always nil, even if I recreate everything from scratch (declaration and control).
It could be that your nib is messed up, but I find a common reason is having two instances where you think you only have one, and the one you're using in your code is not the one you connected.
If you've also defined a loadView method that creates the view, it is possible based on how you initialize it. If you initialize it using alloc-init and the nib name is not the same as class name, then you can have a case where the outlet is nil. But Chuck's answer seems more reasonable to assume.
One reason I just got stung by: If the nib file is not included in the target resource files for some reason (like you had the targets unchecked when you added it to the project), Xcode doesn't throw an error but all the outlets from that nib are going to be null...
One possibility:
Suppose the IBOutlet container is a singleton object with a function like:
+ (singletonObject*) sharedInstance {
if(!gGlobalSingletonPointer) {
gGlobalSingletonPointer = [[singletonObject alloc] init];
}
return gGlobalSingletonPointer;
}
You create the singleton object "on demand" if it doesn't already exist.
You save a global pointer to it, as you create it, in that function.
If you also instantiate such an object in InterfaceBuilder, and connect its outlets, this object will be created without sharedInstance being called. If you subsequently call sharedInstance, a new object is created (sans IBOutlet connections).
The solution is to update the global pointer in singletonObject's init or awakeFromNib function.
Are you using a UINavigationController?
If so, open your MainWindow.xib in IB and make sure that your root controller's nib name is set correctly in the Attributes Inspector.
Why would this not be set correctly? One reason is the 'rename' refactoring doesn't update this, and then the internals won't find the nib with which to wire your UI. Or you renamed the nib yourself, and didn't update this field.
Are you doing something unusual with File's Owner? If you're not in one of the situations where the nib is loaded automatically (main nib loaded by application or nib loaded by view controller, document, or window controller), then you have to load the nib programmatically.

Showing an NSPanel on demand - NSPanel not showing?

Hey guys,
I thought this would be an incredibly simple thing to do, but it's proving trickier than I thought.
I want to be able to show an NSPanel that displays a circular progress bar and the name of the thing that's being processed.
So, I made an NSPanel containing those controls in IB, then I created a subclass of NSWindowController. Created outlets for the controls and linked those up.
However, when I try using this code to display the NSPanel, nothing happens:
[[[self controller] msgSubject] setValue:[msg subject]];
[[[self controller] window] setLevel:NSFloatingWindowLevel];
[[self controller] showWindow:self];
[[[self controller] window] makeKeyAndOrderFront:self];
[self controller] is a method that lazily instantiates the NSWindowController subclass. I tried adding the call to makeKeyAndOrderFront: in vain, but the panel still isn't popping up.
I tried debugging and what I found is that when -initWithWindow: is called, the NSWindow that's passed in as an argument has all zeroed out instance variables, leading me to believe there's some sort of IB linking issue going on here.
Any ideas? I'm guessing I missed something really obvious, but I can't for the life of me figure out what it is.
Please post the contents of the [self controller] method so we can begin to see exactly how you're creating and using the NSWindowController subclass. Are you creating it using the initWithWindow: method directly, or is that method (which is the designated initializer) being called indirectly from one of the other init methods? If you are calling it directly, that doesn't really make sense to me as you said you already created the window in nib file itself. If on the other hand, it's being called indirectly by -initWithWindowNibName:, then it would help to see that code.
NSWindowController's are primarily used/set up in 2 different ways. One way is to create an NSWindow programmatically, and then create the NSWindowController subclass and feed that window in as the window the controller will manage. The second, and more frequently used method, is to create a nib file that houses the window, which is what it sounds like you're trying to do. In this method, you generally use the -initWithWindowNibName: initializer. As long as you pass in the proper nib name (generally without the ".nib" part of the filename), and that nib file can be properly found at runtime, and the file's owner in this nib file is set to be the custom NSWindowController subclass, and the window outlet of this subclass is properly hooked up to your window, then you should be all set.
Might want to double-check to make sure that the nib file you want to load is actually in the app bundle. (I've occasionally forgotten to add it to the target so at runtime the nib file couldn't be located and so the -initWithWindow: method would always show a nil parameter).

Accessing parent view controller (custom) properties

I have written a UITabBarController subclass (called MainViewController) and added a few instance variables, specifically a venue of type Venue. Each tab of the MainViewController needs to access the venue variable of the MainViewController. In the first tab, a UIViewController subclass named HomeViewController, I wrote my viewDidLoad method and tried to output to NSLog with both...
NSLog(#"%#", self.parentViewController.venue.name);
and
NSLog(#"%#", self.tabBarController.venue.name);
But XCode gives the error
error: request for member 'venue' in something not a structure or union
I can access the venue property from within MainViewController just fine so have ruled out an error there. The iPhone simulator does see both self.parentViewController and self.tabBarController as an instance of MainViewController. The line...
NSLog(#"%#", self.tabBarController);
outputs...
2009-06-05 17:54:46.502 Venue[29729:20b] <MainViewController: 0x536600>
Since it's seen as a MainViewController instance, I doubt using casting would help. Is my only other option to initialize the HomeViewController with a copy of the venue or am I just doing something completely wrong with 'self.parentViewController.venue.name'? Thanks, Rob
You're doing something completely wrong. parentViewController is declared as a UIViewController. NSLoging it outputs its real type due to the wonders of polymorphism.
UIViewController doesn't have a Venue member. Your MainViewController does. Casting it is, in fact, the right answer. Make sure to import MainViewController.h as well.
#import "MainViewController.h"
[...]
NSString *name = ((MainViewController *)self.parentViewController)).venue.name;
Also make sure, of course, that venue is declared as a #property of MainViewController, and name is a #property of Venue.
int currentVCIndex = [self.navigationController.viewControllers indexOfObject:self.navigationController.topViewController];
//previous view controller
AccountViewController *account = (AccountViewController *)[self.navigationController.viewControllers objectAtIndex:currentVCIndex - 1];
account.property = object;
[account doSmthng];
Your NSLog() statement is "seeing" it as a MainViewController because that's what the object actually is. The problem you're having though, is that the compiler doesn't actually realize this because the parentViewController property is declared as a plain old UIViewController. Even though you're actually returning a subclass of it, as far as the compiler knows you're trying to call that method on a UIViewController, and it's going to complain. Casting it to a MainViewController will solve the issue.

Is calling UIView's removeFromSuperview enough, or is removing UIViewController required?

I have a main view that manually creates a UIViewController (not a UINavigationController) and adds that controller's view as a subview.
The subview then dismisses itself via [self removeFromSuperview]. I understand this releases the view, so that is good, however I now want to get also get rid of the UIViewController I alloc'ed immediately when the view is removed to free up memory and not wait until the main view controller is dealloc'ed.
I could avoid using removeFromSuperview and have a backreference to the main view controller and get it to dismiss the subview and release the controller, but it feels like there should be a cleaner way.
Is there an established best practice for accomplishing this?
The correct way is for your subcontroller to ask the main controller to remove it. If you want to reduce the coupling between the two controllers, create a delegate protocol for your subcontroller:
// This forward declaration avoids having a circular dependency
// between MySubcontroller and MySubcontrollerDelegate
#class MySubcontroller;
#protocol MySubcontrollerDelegate
- (void)hideMySubcontroller:(MySubcontroller*)subcontroller;
#end
If there is other information that the subcontroller needs to communicate to the supercontroller, this is a great place to add relevant calls. You might not need to do so right away, but keep it in mind for future versions of your app.
Then add a delegate property to the subcontroller:
#interface MySubcontroller : UIViewController {
id <MySubcontrollerDelegate> delegate;
...
}
#property (assign) id <MySubcontrollerDelegate> delegate;
...
#end
Instead of calling removeFromSuperview on its view, the subcontroller should call hideMySubcontroller: on its delegate, passing self as the argument.
Your main controller should then declare that it implements the delegate protocol:
#interface MyMainController : UIViewController <MySubcontrollerDelegate>
...
#end
When the main controller creates a subcontroller, it should set the subcontroller's delegate property to self. It should implement a hideMySubcontroller: method which removes the subcontroller's view, deallocates the subcontroller, and does whatever else is needed.
Using a delegate protocol means that the subcontroller doesn't have to have any knowledge of what kind of object will use it; it just knows that there is such an object somewhere and that it will conform to the delegate protocol. Thus, the coupling between the two controllers is kept as small as possible.
By the way, if you can manage it, it's actually better to keep the subcontroller around in case you need to use it again; that'll save the processing time it would take to recreate it. (However, you should release it if you receive a memory warning.)