Swift class to interact with Interface Builder via IBOutlet - xcode

Good evening all,
I'm slowly working through my first OS X app. I have been having a hard time getting my Swift class to interact with an NSPopUpButton. Just to make sure I was doing it right, I created a new project and successfully erased all entries and entered text into the NSPopUpButton via AppDelegate. However, as soon as I try to move the same functionality to my own class, I can't even get the IBOutlet connection across to the new class.
Is a particular subclass type required of a new class to work properly with interface builder?
Here is a screenshot of the class I have created, as well as AppDelegate where I am trying to call the function belonging to this class.
Finally, here is the IB element in question, should I be able to select my own class under the 'Custom Class' inspector?

I am an iOS developer, but I would imagine the same principles would apply to your situation.
A ViewController class and an interface created in interface builder are two seperate things. While it may appear that they are connected via an iboutlet, they are actually independent and one can be instantiated without the other.
Currently, you are only creating an instance of your ViewController class in your App Delegate - and that's all. The system has no idea that your xib even exists! The outlets will only be connected once your app connects your xib to your ViewController class.
How do we do this? It's actually quite simple. Instead of instantiating our view controller like this:
let viewcontroller = ViewController()
We would connect our view controller to our xib in the instantiation:
let viewcontroller = ViewController(nibName: "MainWindow", bundle: NSBundle().mainBundle)
The nibName is telling the system the file name of your xib, and the NSBundle().mainBundle is telling the system where to look for the xib.
This will all only work if your xib has been assigned a custom class, like you mentioned. In your xib in interface builder, select the entire view controller. Then, in the custom class inspector type in the name of your ViewController class (in your case: ViewController - it should autocomplete). This will make sure your outlets are connected.
And you should be set up!! Let me know if you have any more problems come up.
Good luck!
EDIT:
This replaces the first part of my answer, however the part about hooking things up in Storyboard remains true. Upon reconsidering, I've believe I've realized that we are only creating the view controller, and not adding it to our view. Despite this, I believe we can take a short cut solution by adding one method to your view controller subclass (the one we set in the Storyboard). Start typing in viewDidLoad, and it should autocomplete. Type in super.viewDidLoad() at the beginning of the method. After that, type self.listUpdate(). This should work if the classes are hooked up correctly in Storyboard. This means you can delete the variables you created in the App Delegate.
Reference: You might also find Apple's documentation on creating a view controller handy (it's in Objective C online, but can be easily converted to Swift - it's the concept that counts): NSViewController Class Reference

Related

Swift, storyboards and core data...a missing persistentstorecoordinator?

I am working on a new Cocoa project using Swift, Core Data and storyboards, and have come across a problem that makes no sense to me. After some fairly extensive hunting around, including on this site, I have come to the conclusion that I must be missing something obvious, but cannot figure out what. Here is what I have done so far:
1.Create a new project, Cocoa Application, using Swift, Storyboards, and Core Data.
2. Create an entity in the .xcdatamodeld. Let’s call it Dataset.
3. Create a subclass of NSSplitViewController (for what i want to do in the rest of the program).
4. Set the window content of the main window to and instance of myVC. I checked, and it loads up and displays fine.
5. In the viewController.swift, get the managedObjectContext like so:
#IBOutlet var moc:NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let appDelegate = NSApplication.sharedApplication().delegate as AppDelegate
moc = appDelegate.managedObjectContext
println("mainsplitviewcontroller moc:")
println(moc)
println("mainsplitviewcontroller psc:")
println(moc.persistentStoreCoordinator)
NSLog("Main split view loaded")
}
(yes, I have about dependency injection, but I want to solve this problem first).
In IB, put a managedObjectContext object in the View Controller instance.
In IB, connect the myVC outlet for the variable moc to the managedObjectContext.
In IB, create an array controller. Set it To Entity. Entity Name is Dataset. Turn on Prepares Content.
Either as an outlet or a binding, connect the array controller to the MOC. Using outlet, just dragging from managed object context in it's right-click popup to the icon for the MOC created in 6 above. For bindings, the old fashioned way, going to the bindings tab, and under Parameters, Bind to: (view controller), Model Key Path: moc. (moc is from 5 above)
Then, I build and run. and I get the error: "Cannot perform operation since managed object context has no persistent store coordinator."
This happens whichever way I try to do 9, above.
Now, the thing is, from my println statements, the objects referred to in both the app delegate and the viewcontroller are the same, both for the managed object context and for the persistent store controller, as below:
appdelegate moc:
appdelegate psc:
mainsplitviewcontroller moc:
mainsplitviewcontroller psc:
I wish I could show images, but I am new here and so cannot do that. Am I doing something clearly wrong? I thought I understood the process: make sure the VC can access the MOC, then put the MOC object into the VC's window in IB, make it an outlet, and connect it to an array controller. Why would the swift file for the view controller seem to show that the PSC is the same as the app delegate, but in IB, the array controller think the MOC has no PSC at all?
Thanks for reading!
I don't know if this is going to help, but I'm not surprised that your project shows that error. You have two managed object contexts - one created by your app delegate, and one created by the storyboard. Your interface code is connecting to that second MOC, which is not connected to your persistent store.

How to manage windows in cocoa

I know the question is a bit generic but I guess my issue is generic as well.
I'm developing a small application in my free time and I decided to do it with Cocoa. It's nice, many things works almost automagically, but sometimes it's quite hard to understand how the framework works.
Lately I'm facing a new problem. I want to manage all the windows of the application from a single class, a front controller basically. I have a main menu and an "Import data" function. When I click it I want to show another window containing a table and call a method for updating the data. The problem is that this method is inside the class that implements the NSTableViewDataSource protocol.
How can I have a reference to that class? And more important, which should be the right way to do it? Should I extend the NSWindow class so that I can receive an Instance of NSWindow that can control the window containing the table (and then call the method)?
I may find several ways to overcome this issue, but I'd like to know which one is the best practice to use with cocoa.
PS: I know that there are tons of documentations files, but I need 2 lives to do everything I'd like to, so I thought I may use some help asking here :)
The problem is that this method is inside the class that implements the NSTableViewDataSource protocol.
How can I have a reference to that class?
These two sentences don't make sense, but I think I understand what you're getting at.
Instead of subclassing NSWindow, put your import window's controlling logic – including your NSTableViewDataSource methods – into a controller class. If the controller corresponds to a window, you can subclass NSWindowController, though you don't have to.
You can implement -importData: as an IBAction in your application delegate, then connect the menu item's selector to importData: on First Responder. That method should instantiate the import window controller and load the window from a nib.
In your import window controller's -awakeFromNib or -windowDidLoad method, call the method which updates the data.
Added:
Here's the pattern I'd suggest using in your app delegate:
#property (retain) ImportWindowController *importWC;
- (IBAction) showImportWindow:(id) sender {
if (!self.importWC)
self.importWC =
[[ImportWindowController alloc] initWithWindowNibName:#"ImportWindow"];
[self.importWC refreshData];
[self.importWC.window makeKeyAndOrderFront:sender];
}

initWithNibName either called twice or wrong xib loaded

I'm programming a Cocoa application and want the application to work as a kind of Wizard. So in the main window I have a Custom View that interacts with the user and changes from a sign in to a device activation screen as they step through the stages of the wizard. I have currently overridden the WizardViewController's awakeFromNib method:
- (void)awakeFromNib{
//If no redirect request save, add first view: ID Login
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *tokenRequest = [defaults objectForKey:#"redirectRequestToken"];
if (!tokenRequest){
SignInWithIDViewController *signInViewController = [[SignInWithIDViewController alloc] initWithNibName:#"SignInWithIDViewController" bundle:nil];
[wizardView addSubview:[signInViewController view]];
} else {
NSLog(#"Have already logged in.");
}
}
As is, initWithNibName in SignInIDViewController gets called twice, once explicitly by me, and again when the view is loaded (presumably through loadView). However, if I simply call init then initWithNib name is only called once, but the wrong xib file is loaded (of the DeviceActivationViewController class). I can't seem to figure out what I'm doing wrong, because the signInViewController should not be init twice, but I need the proper xib file in IB to display.
The only other method I have in this class currently that is not a user interface IBAction is the generated initWithNibName method plus an added NSLog statement.
I think that creating the objects in IB (the blue cubes), and instantiating them in code is the problem. If you've created objects for them in IB, then they will be instantiated in awakeFromNib, you shouldn't also call alloc init on them in code -- that will create a new instance.
I don't have a lot of experience with using view controllers in OSX, but it seems that you can't connect IBActions to the view controller (as file's owner). The way I made it work, was to subclass the custom view (that's created for you when you add a view controller), change the class of that view to your new subclass, and put the action methods in that class. It seems like this should be something that would be handled by the view controller, but I think it not working has something to do with the view controller not being in the responder chain in OSX (whereas I think it is in iOS).
After Edit: After a detour into memory management problems, I think I found the best way to do this. You can, and probably should (to comply with Apple's MVC paradigm) put the button methods in the view controller class rather than in the view as I said above. You actually can connect the IBActions to the view controller (as File's Owner), you just need to make sure that the view controller is retained when you instantiate it in code. To do this, you need to make signInViewController a property in whatever class you're instantiating the SignInViewController class in, and use "retain" in the property declaration. Then you don't need to (and shouldn't) create any of the blue cubes in IB.

How do I programmatically change a custom view object's member variables?

I appreciate you taking the time to read this and hopefully help me out!
I am writing my first program in Xcode 4, using Objective-C and Cocoa for the first time. I have been following the tutorials, but at this point I want to display custom graphics using a custom view, where the graphics change based on the values in my model object.
My controller class, GameController, based on NSObject, creates my model object during initialization, and successfully receives button action messages, updates the model object appropriately, and updates text strings on the window in return using its outlets. I accomplished this by creating an NSObject object in my xib file (which is called the default (?) name of MainMenu.xib, but it includes all the UI in my application), using the utilities pane to set its class to GameController, and then connecting its actions and outlets to the appropriate UI elements.
I have dragged a custom view into my window, and used the utilities pane to set its class to my custom view class, BoardView, which is based on NSView. In BoardView, I have overridden drawRect: to fill in the background of my view with a blue color, and then draw each of the graphics defined by my model class (GameStatus).
The problem is, I do not know how to access the data in GameStatus from my BoardView class, or have GameController update a member variable of BoardView. I have not instantiated BoardView anywhere besides in Interface Builder, by dropping a custom view on my window. I do not have a BoardView object in my xib file (the column or list on the left side in Interface Builder).
I tried to create an outlet to a BoardView object in my GameController class (which does have an object in my xib, as I mentioned above), and connecting that outlet to the BoardView custom view in my window, and Interface Builder seemed fine with that. However, when I run the program, the value of the BoardView outlet pointer in my GameController class is 0x0. It seems like the outlet is not being connected to a BoardView object. I don't know if I need to do something else in Interface Builder to make an actual object (I tried creating one in the list to the left, but then couldn't figure out a way to connect it to the actual custom view displayed on the window).
To add to the confusion, when I run my application, the BoardView area of the window will display the blue background, and in fact any other graphics which I define in the drawRect: function. However, without any way to talk to my model object, I can't change the graphics based on the state of the model. I'm not sure if the fact that the hard-coded graphics are displaying correctly means that there is an object there somewhere, or whether it is somehow drawing based on the general template of the class somehow. The latter doesn't really make sense to me, but if the former is true, I'm can't figure out how to talk to that object from other parts of my code.
I feel like I'm just missing some basic understanding of how Xcode / Interface Builder creates objects or something. I would really appreciate someone telling me exactly what I'm missing here in the connection between my MVC objects / classes.
Thank you in advance for any help you can give me!
EDIT 2011/09/06:
To download a copy of my project to take a look at it, go here:
http://talix.homeip.net/2011/rival/
That's my home server and I'm an amateur at this, so please leave a comment if it isn't working. Thanks! Also, if there is a better way to respond to comments other than editing my original post, please let me know; I'm also new to this website. ;-)
-Joe
It sounds like you're only instantiating one GameController and one BoardView, and that's happening in the nib.
This, then, is the heart of the problem:
I tried to create an outlet to a BoardView object in my GameController class (which does have an object in my xib, as I mentioned above), and connecting that outlet to the BoardView custom view in my window, and Interface Builder seemed fine with that. However, when I run the program, the value of the BoardView outlet pointer in my GameController class is 0x0. It seems like the outlet is not being connected to a BoardView object.
Where in your code are you needing a reference to your BoardView but getting nil? Is that in GameController's init method, or in some other method of GameController?
If it's inside init, this is what I'd expect. The code that loads nibs must initialize the objects before it connects the outlets.
(If it's outside of init, I'd suggest starting by disconnecting and reconnecting the outlet.)
View = BoardView
Controller = GameController
Model = GameStatus
In MVC, the controller usually brokers communication between the model and the view, so I suggest you handle it this way:
Add a gameController outlet to BoardView and connect it to your game controller.
In drawRect, have the BoardView get the game status from the game controller.
I also suggest you make GameController your application's delegate:
Delete RivalAppDelegate.[hm].
In your nib, delete the reference to Rival App Delegate, and connect the Game Controller reference to the File's Owner delegate outlet.
You've got two instance of BoardView in your nib. Hook up the one inside your window to the boardView outlet of GameController and delete the other.
In GameController.h, after #interface GameController : NSObject, add <NSApplicationDelegate>
In GameController.m, implement applicationDidFinishLoading and set up your application there. (You can even call setGameStatus there if you want to.)

IBOutlet not getting set in Cocoa MVC project

I might not be using IBOutlet correctly, or some other subtlety with how NIB files work is causing me trouble - any help would be much appreciated (feel free to propose an alternate way to accomplish what I want).
I have a View object and a Controller object. Both are in the NIB. The Controller's init is also called when the NIB is loaded and the View is initialized in the 'awakeFromNib' callback.
I need a way to connect these two objects - specifically, enable the 'View' object to call functions on the Controller.
Based on documentation online, the way to get these connected is to define an IBOutlet in the View and connect it to the Controller in the Interface Builder. So i created an
IBOutlet Controller* _controller;
in the View interface and graphically connected it to the Controller object in Interface Builder by making a connection from the View to the Controller and assigning the _controller outlet to the Controller (the blue Generic Object box in Interface Builder).
At runtime though, _controller is always _nil. I have verified that the Controller's init was indeed called.
Is there something obvious I'm missing about this?
Any simpler way to connect these two? Since they're both created by the NIB I don't have a common object that has a pointer to both.
Try accessing the IBOutlet in viewDidLoad instad.
When awakeFromNib is called not all the IBOutlets are populated (even though the documentation seem to imply it).

Resources