I have an NSArrayController bound to CoreData in my application. It is also bound to a TableView that displays the data. Two buttons are bound to the ArrayController that add and remove lines. All of this is working as expected. I can add, edit, save, and remove CoreData Entries.
There is a section of my app that is to accept drag and drop operations from files (working). It takes the data from the files, looks for various information, and is to insert this information into the Core Data database via the NSArray Controller.
I have added the class handling the parsing/adding of the file to the database as an object in IB. I created an IBOutlet for the array controller in the class, and bound the controller to the class' referencing outlet.
If I add a button to the interface to directly call the method that adds a custom record to the database, everything works. If the method is called via the drag and drop operation, nothing works, even logging a simple [arrayController className] returns null (though returns NSArrayController as expected when the method is called from the button click).
The only difference I can see is that when accessed through the button click, the method is called directly, while the other way passes through my drag and drop class before loading the parsing class, but I'm completely stuck on how to remedy this situation. I'll be happy to provide code, just not sure which code you'll need.
Any help is appreciated. Thanks!
==================
UPDATE
turns out I was connecting the IBOutlet to a class (a subclass of a view) object in IB instead of to the view itself handling the drops. Connecting these up made things work. Well, not work, I have other issues to iron out now, but the Array controller is now instantiated.
Moved from comment to answer: The array controller you are trying to add stuff is not instantiated. I assume you are not referring to your original NSArrayControllerinstance but maybe a new created one? Probably a problem of communication between your class instances.
Debugging this should be straightforward ... using the debugger. Set a few breakpoints (one at each action the button(s) call, and one at each point where your class instances are meant to talk to each other (your importer and your main controller)). Run, test, step through the code when the debugger breaks at each breakpoint.
My guess: An outlet is not hooked up (is nil) in IB or is not yet reconnected at runtime (see -awakeFromNib and make sure you're not trying to touch an outlet or action that hasn't been fully reconnected from the nib at runtime by the time you're trying to use it).
Something’s not hooked up right, BUT you don’t want to do it this way anyways. There’s no advantage to inserting via an NSArrayController. Just create new objects with NSEntityDescriptions:
+ (id)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;
And you’re done. If your NSArrayController is hooked up correctly it’ll auto-fetch the new objects at the end of the event so the user will see them “immediately.”
Related
I am pretty new to Core Data and am currently working on a small (OSX) app that uses an NSTableView to organise objects. I would now like to delete a row/object with the click of a button on that targeted row.
I access the managed object within the table controller by calling [NSApp managedObjectContext] (still trying to figure out that dependency injection thing) but I can't easily delete an objectAtIndex: like I used to with the array (which has now been replaced by the core data stack, right?).
How do I identify the object to be deleted? And consequently, how can I cleanly remove it from the stack?
This is probably a really basic question but I couldn't find any resources on it. Bindings obviously don't work because the row does not get selected before the click occurs.
Any help is much appreciated!
Bindings would work, in that you could have the button's IBAction query the objectValue for the parent NSTableCellView. Once you have that objectValue, you could call the bound arrayController to delete the object, and then the cell/row would disappear.
So, if you have a tableCellView that has a delete button with an IBAction, within that IBAction, you could get the sender's superview, ensure it's an NSTableCellView, get the objectValue, and call [myArrayController removeObject:...]
As it says in the NSTableCellView class reference:
The objectValue is automatically set by the table when using bindings or is the object returned by the NSTableViewDataSource protocol method tableView:objectValueForTableColumn:row:.
This is actually a typical pattern with views in cocoa. objectValue or often representedObject are properties on the views that refer to the data model objects they represent, so if you have a view pointer from sender on the IBAction, you can get the related data model object. And if you're using bindings and a controller, you can then just have the controller remove that object.
With bindings, you will often create buttons that need IBActions attached, rather than some direct binding. But those IBActions can most definitely interact with the controller and not the view.
And with core data, array controllers are really slick vs. assuming you have to do it all programmatically.
I have a problem when reloading views. OSX is not as simple as IOS.
In MainMenu.xib I have an NSBox. As per Hillegrath, several views are stored in an array and when a segmented button is pressed the views are exposed. This works properly. My frustration involves revisiting these views when changes have been made that should cause them to redisplay new values through [self someAction]. Actions in two of the views alter (global)values that should propagate changes in the other views. I am using a window controller w/xib (MainMenu) to hold the box which contains the views. I also title the views in code.
The global ivar values change properly when required and the log shows that. However dependent operations do not occur when the view are revisited, ie, update view specifics.
What appears to happen is that loadView is not called when the various views are displayed, ** **. awakeFromNib and loadView operate correctly when each view is first displayed but not ever on redisplays. This implies that that the view may be hidden but viewDidUnhide has no effect.
The view changing code (from an SO MVC answer and Hillegrath) is
NSViewController *activeVC =
(NSViewController *) self.viewControllers[index];
// [_box setContentView:nil];
[_box setContentView:activeVC.view];
[_box setNeedsDisplay:YES];
From the copied code it can be seen that I have also attempted setting the active content to nil before setting a new view but, to no avail.
Any ideas, recommendations, notifications fail to work either but may not set up correctly.
Thanks
It looks like you’re confusing view loading with display. -awakeFromNib is received by a nib file object only when the nib file is loaded. Similarly, -loadView is received by the view controller only when its corresponding nib file is loaded. Since you are keeping your view controllers in an array, those view controllers do not get deallocated, hence their corresponding nib files are never unloaded whilst the array is alive. This is reasonable behaviour, but you must bear in mind that -awakeFromNib and -loadView are only executed once in this case because nib loading is executed only once.
Since it seems that you are manually populating the view in -loadView, you’ll also have to do that whenever you set that view as the box’s content view (assuming you’re not always updating the view whenever the model changes). For instance, you could have a -reloadData method in your view controller and both -loadView and your box’s content view swapping method would use -reloadData.
Alternatively, you could set your model object as the represented object of your view controller and bind the controls in the corresponding view to properties of that represented object. NSViewController exposes a representedObject property that’s convenient for bindings.
For the record, -[NSBox setContentView:] marks the box for redisplay, so you don’t need to send it -setNeedsDisplay:YES.
(and make sure _box actually points to the NSBox instance)
I'm new to Cocoa and Objective-C, so I'm following the Lynda courses and learning a lot. Thing is, I've encountered a problem that I cannot figure out, even though I seem to be doing it exactly the same way...
Basically, I'm trying to get a Table View hooked up through bindings to an Array Controller, just to list out the contents of a simple NSMutableArray in my code. I'd gotten it all hooked up properly, but no matter what I did it wasn't displaying anything when I ran the program.
Here's where it gets weird: on a lark, I added a "+" button and hooked it into the "add" function of the Array Controller, and when I ran the app and clicked that button, it not only added a new line, but it displayed the whole array as well! Apparently everything had been hooked up properly the whole time, it just wasn't displaying the information. Further experimentation revealed that I could make any changes I wanted to the array, whether in the original code or during the runtime of the app, but they would only be updated in the Table View when I clicked that "+" button.
I feel like this is probably a simple solution, just some "Continuous" box that needs to be checked or something, but I cannot for the life of me find it... Can anyone point out what I need to do to get my TableView to show its contents automatically?
(Also, I don't know if this is related or not, but none of the "Model Key Path" fields in the inspector are offering suggestions as I type, which they do in the Lynda course. The app works fine if I manually type everything in, but it says "no completions found" the whole time.)
Thank you in advance for helping out a n00b!
none of the "Model Key Path" fields in the inspector are offering suggestions as I type
As I understand it this is probably because the NSMutableArray that holds your data array i.e. dogPound or similar, isn't also declared as a property, only an instance variable.
Declare the property #property NSMutableArray * dogPound; and change the instance variable declaration to _dogPound and I think interface builder should offer you the auto-completes.
I'm new to Cocoa and Objective-C
Me too.
I'd gotten it all hooked up properly,
In about 30 minutes, I can get everything setup with a custom class like Dog, and another class called AppController that consists of one instance variable: NSMutableArray* dogPound. The init() method for the AppController class creates the array and adds some Dog instances to the array. I also bound an NSArrayController to the dogPound array, and I bound the NSTableView columns to the NSArrayController. After I Build&Run the NSTableView displays the information for each Dog instance in the dogPound array.
I also tried a simpler version where there is no Dog class and the array in the AppController class just consists of some NSString objects. Once again, I was able to successfully bind an NSArrayController to the array and bind the table's columns to the NSArrayController, so that an NSTableView displayed all the NSString's in the array.
You need to post your exact code, and you need to write down every step you did in IB, which of course is a huge pain in the ass, but it's the only way anyone will be able to help you.
Here's where it gets weird: on a lark, I added a "+" button and hooked
it into the "add" function of the Array Controller, and when I ran the
app and clicked that button, it not only added a new line, but it
displayed the whole array as well!
Of course. The add: method in the NSArrayController adds a new item to the array and then signals the NSTableView that it should reload the data, i.e display everything that's currently in the array.
I feel like this is probably a simple solution, just some "Continuous"
box that needs to be checked or something,
Nope, nothing like that.
none of the "Model Key Path" fields in the inspector are offering
suggestions as I type
Lack of autocompletion choices is a big hint that you are doing something wrong--even though I find I can't always figure it out, so I just keep typing. Did you remember to start your bindings in the Attributes Inspector(Object Controller section) for the NSArrayController? In IB, did you create an instance of your AppController class, or whatever you called the class that contains the NSMutableArray, by dragging an Object onto MainWindow.xib?
I am using MonoMac to build a desktop download manager for Mac in C#.
My XIB has a Table View, whose columns are bound to an NSArrayController. The array controller is connected to my Main Window Controller through an IBOutlet. The array holds a bunch of HttpDownload objects, which derive from NSObject. These HttpDownload objects contain properties such as TotalSize, TotalDownloaded, Bandwidth, etc. I have decorated these properties with an [Export] attribute.
In the controller I add some HttpDownload objects to the NSArrayController using the AddObject method. A background process, started with Task.Factory.StartNew() begins the download asynchronously and updates the bound properties such as TotalDownloaded and Bandwidth as data is received.
I can see these new values being reflected in the Table View, but only once I've "forced" a UI update, for instance by causing the window to lose focus, gain focus, or by clicking on a button within the window.
I have tried setting Continuously Updates Value in IB, but this makes no difference (and reading the docs, I didn't think it should).
Does anyone know to make the UI update the bound values in "real time", instead of only when a window event occurs?
I figured this out shortly after I posted this question.
It seems that we need to manually call WillChangeValue() and DidChangeValue() for at least one of the keys that are being updated, for instance, when I updated the total downloaded:
WillChangeValue("DownloadedBytes");
DownloadedBytes += bytesRead;
DidChangeValue("DownloadedBytes");
In my case, calling these methods for just one of the updated keys seems to be enough to force an update of all the bound values.
For reference, in Objective-C these selectors are called [self willChangeValueForKey:#"keyname"] and [self didChangeValueForKey:#"keyname"].
In my Cocoa app, I have a sheet with a one-column NSTableView that lists a bunch of files in a directory (the app makes back-ups of it's main database, provides this list to users so they can revert to a particular back-up). The content is loaded into and provided to the table view by an NSArrayController, each object is just an NSFileWrapper (I'm considering using NSURL instead, but I digress). The NSArrayController handles sorting, enabling the buttons when a row is selected via bindings, that's all great. I have an NSWindowController subclass object (BackupsSheetController) that hooks all this up and exists in the sheet's nib.
However, when a user edits one of the cells, I want to respond to that change from BackupsSheetController by appropriately re-naming the file represented by that cell, putting it in its new location. Since the table view is bound to the NSArrayController, I don't get sent the NSTableViewDataSource message – tableView:setObjectValue:forTableColumn:row:. If I set my BackupsSheetController as the datasource for the NSTableView object in the nib, I get sent that message sometimes, but not very often, to say nothing of every time.
Most questions and examples I see out there for this scenario handle this all by using a custom model class for items in their table view, and make some controller object an observer for changing properties that they wish to respond to. In other words, each item would be something like a BackupNode object, and BackupsSheetController would observe each for changes to the name property (or whatever I would call it). That seems totally overkill for my scenario, but I also don't want to ditch the bindings I've already got in use and I don't see another way to do this. Is there another way to do this, to make sure I reliably get the setObject:... message? Or should I drop the NSArrayController and make BackupsSheetController the delegate and datasource for the table?
In the "BackupNode" scenario, I don't see why BackupsSheetController would observe each for changes in its name. That's a very roundabout way of doing things. I would think that the hypothetical BackupNode object would simply do the necessary work in its setter for the name property.
Anyway, I recommend using proper model objects. When you try to build a model with only Cocoa-provided objects like NSFileWrapper, NSURL, or NSMutableDictionary, you end up doing more work in the long run than if you just make a proper model object.
On a tangential topic, why is your window controller in the NIB? It should be the thing which loads (and owns) the NIB, which of course requires that it exist prior to the NIB being loaded, which means it can't be instantiated in the NIB.