NSObjectController - setAutomaticallyPreparesContent: - nsobject

I have a small core data app. I have a subclass of an NSObjectController acting as the binding between the view and the model and a NSTextField on the view for the user to type into.
When the window opens the text field is editable because I have the bindings set to my subclassed NSObjectController, controller key to "content" and the Model Key Path to an attribute of my Entity.
If I type in some text, save the file and reopen it the text in the NSTextField isn't there.
For testing, I have a button connected to the add: selector of the controller and when you press the button everything works fine - you can enter text into a NSTextField, you can save the document, you can open it again.
How do I prepare the object when the nib loads?
In my init method in my subclass of the NSObjectController I have:
[self setAutomaticallyPreparesContent:YES];
and then I have in MyDocument:windowControllerDidLoadNib (oc is the IBOutlet to the subclassed objectcontroller in IB):
[oc fetchWithRequest:nil merge:NO error:&error];
but it didn't work. I need to create the content so the user can get started typing.
Thanks

OK, here's my contribution. I'm happy to be able to answer something! I found this in the "NSPersistentDocument Core Data Tutorial" in the documentation.
Remember, my problem was when the document is created I want to create an NSManagedObject. That way the user doesn't have to hit the "add" button. I don't want the document to be dirty (until the user types something) and I don't want to replace the content if I'm opening a saved file. This needs to happen only when a new document is created.
NSDocument provides a method:
initWithType:error:
to accomplish this.
1) grab the managedObjectContext,
2) turn off undo for the moment. This prevents the doc from being dirty and preventing the user from undoing the creation and insertion of the Entity.
3) use insertNewObejctForEntityForName:inManagedObjectContext
4) install the changes
5) turn back on undo
Here's the code:
- (id)initWithType:(NSString *)typeName error:(NSError **)outError
{
self = [super initWithType:typeName error:outError];
if (self != nil) {
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
[[managedObjectContext undoManager] disableUndoRegistration];
self.myManagedObject = [NSEntityDescription insertNewObjectForEntityForName:#"myEntityName"
inManagedObjectContext:managedObjectContext];
[managedObjectContext processPendingChanges];
[[managedObjectContext undoManager] enableUndoRegistration];
NSLog(#"initWithType");
}
return self;
}

Related

Instant / Autosave in IOS

For background: I'm a Windows automation and data translation "expert" (or so they say grins) in my day job. I've been dabbling with Objective-C coding off and on since I bought my first Mac in 2004.
I'm working on an IOS app. My data container class knows how to save and load from disc, and each object responds to an instance method of -(void)saveToImpliedFilename{} or -(void)save:(NSString *)filename {}. There's a static call to load the data files from storage and create distinct data objects from them (they're fairly lightweight objects, so I'm not worried about loading several at a time). The app's domain is such that many of them won't ever be loaded at once anyway.
+(NSArray *)loadData {}
That's all working fine and wonderful. In storage the objects are stored as Xml and life is good.
Where I'm having trouble is when trying to modify the tutorials so that two things happen for me:
Quick note: I'm using the tutorial as a basis for POC coding, then I'll go back and start over with the "real" coding, reusing my data objects and some of the other utility I've built along the way.
Here's my list of goals and the issues:
I want the table view to tell the data objects to save at pretty much every "edit" event. The only one I can consistently get to work is reorganizing the table's order. (the save button and adding a new entry works fine)
entering a new entry into the list creates a nice modal editor with a save and a cancel button which work wonderfully. But if I edit an existing entry, I can't reproduce the save buttons' behaviors. Each time I try, the buttons' events no longer fire. I can't figure out where I'm going wrong.
I'm using the "Editable Table View" project from this tutorial series as my basis: http://www.aboutobjects.com/community/iphone_development_tutorial/tutorial.html
In the following code, the [self isModal] test is where the save/cancel buttons are made visible and wired up. Bringing up the new-entry screen is apparently the only time it's modal. I tried wiring this stuff up so that the buttons were created all the time, but again, the events never fire for either one. The next block below is where the editable table view is called explicitly with the NEW functionality, but the nonModal view of the same tableview is called by the select event on the selector table.
So...
// code snipped for the new/modal editor
- (void)viewDidLoad {
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
// If the user clicked the '+' button in the list view, we're
// creating a new entry rather than modifying an existing one, so
// we're in a modal nav controller. Modal nav controllers don't add
// a back button to the nav bar; instead we'll add Save and
// Cancel buttons.
//
if ([self isModal]) {
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(save)];
[[self navigationItem] setRightBarButtonItem:saveButton];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(cancel)];
[[self navigationItem] setLeftBarButtonItem:cancelButton];
}
// do stuff here to display my object...
}
// this code is called from the selection table to explicitly add a new data object.
- (void)add {
vhAddVehicleViewController *controller = [[vhAddVehicleViewController alloc] initWithStyle:UITableViewStyleGrouped];
id vehicle = [[Vehicle alloc] init];
[controller setVehicle:vehicle];
[controller setListcontroller:self];
UINavigationController *newNavController = [[UINavigationController alloc] initWithRootViewController:controller];
[[self navigationController] presentViewController:newNavController animated:YES completion:nil];
}
// this is where it's called on the table selection to show the same view without the save/cancel buttons.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
vhAddVehicleViewController *controller = [[vhAddVehicleViewController alloc] initWithStyle:UITableViewStyleGrouped];
NSUInteger index = [indexPath row];
id vehicle = [[self vehicles] objectAtIndex:index];
[controller setVehicle:vehicle];
[controller setTitle:[vehicle Vehiclename]];
[[self navigationController] pushViewController:controller animated:YES];
}
I'm assuming the issue is that presenting it makes it modal, where as pushing it doesn't...? That's fine. But when I take out the test for modal to try to keep the buttons working, no joy. The buttons draw and click when tapped, but the events don't fire.
HALP! :-)
Thanks much.
-- Chris (I logged in with my Google account so at the top of the page I'm showing as "user1820796") shrug
You forgot to call [super viewDidLoad];
Update
Try removing the cancel button that goes on the left side when pushing the view controller. See if save starts working. I think the problem is you should not add a left button to the navigation bar when the view controller is pushed.
Which method signature are you using?
- (void)save
{
NSLog(#"Saving");
}
Or
- (void)save:(id)sender
{
NSLog(#"Saving");
}
I still think this was related to push/popping the view rather than presenting the view. I switched it all to presentation and it's working how I want now.
Thanks for the assistance guys. Quite a different paradigm than I'm used to on the GUI stuff, but I'm getting there.
thanks!

Delegating Outline View's Data Source To Separate Object

I want to be able to use a blue object box to delegate control over an NSOutlineView. The blue object box would be hooked up to my primary controller, so it'd just be a data source and control the content of the NSOutlineView.
The problem I'm having is that I have no control over the Channel Data Source. I'm simply calling a declared method with some test NSLog inside of it, and it doesn't get called. The outlet doesn't get instantiated.
Here's the connections of the blue object box (ChannelDataSource)
Here's the connections of File's Owner for my primary controller.
So you see, I want to do something like [dataSource callMyMethod]; with the final aim that I have control over the contents for the NSOutlineView.
Any ideas?
EDIT
The application is structured whereby my primarily app delegate looks like this:
#implementation MyAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MainController alloc] init];
[controller showWindow];
}
#end
Then in the MainController I have something along the following lines:
#implementation MainController
-(id)init {
self = [super init];
if (self) {
// loads of random stuff
[dataSource myMethod];
}
return self;
}
So "Channel Data Source" blue object box is dataSource. At this point in the application life cycle, it's null, which isn't what I was expecting. At the same time, it's still a bit of black magic to me. If you have a blue object box, at what point is it instantiated? Obviously this isn't hooked up correctly though.
EDIT EDIT
Further to my points above, and trying to fix the problem, is this actually a good way to go about it? I'm looking at this thinking it's not meeting a decent MVC architecture, because ultimately the blue object box's owning class is storing and managing the data. Is there a better way to go about managing what's in your NSOutlineView?
EDIT EDIT EDIT
So I have my app delegate, which is strangely a class all by itself that instantiates the main controller. Don't ask me why I did this, it was very early code. So my app delegate (root entry point) has this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MyController alloc] initWithWindowNibName:#"MainWindow"];
[controller showWindow:nil]; // this doesn't open the window
[controller loadWindow]; // this does open the window
}
And the declaration of the controller:
#interface MyController : NSWindowController
Which contains the following method declaration in it:
-(void)windowDidLoad {
[dataSource insertChannel:#"test" forServer:#"test2"];
}
I have a breakpoint in windowDidLoad and it definitely doesn't get called.
Ideas?
There's still a few things you didn't clarify, but I can do some guessing. First, I'm assuming that MainController is a subclass of NSWindowController. If so, you should be using initWithWindowNibName: instead of just init, otherwise how would the controller know what window to show when you address showWindow: to it? Second, even if you do that, and change your init method to initWithWindowNibNamed:, what your wrote won't work, because the init is too early in the process to see your outlet, datasource. If you just log dataSource it will come up null. A better place to put that code would be in windowDidLoad, as everything will have been set up by then (this will be called after showWindow:). So, in my little test project, this is what I did.
In the app delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.cont = [[Controller alloc] initWithWindowNibName:#"Window"];
[self.cont showWindow:nil];
}
In the Controller.M I have this:
- (void)windowDidLoad {
NSLog(#"%#",self.dataSource);
[self.dataSource testMethod];
}
In IB, in the Window.xib file, I set the class of the file's owner to Controller, and the class of the blue cube to ChannelDataSource. EVerything was hooked up the same way you showed in your post.

NSOutlineView + DataSource properly setup. How to add ImageAndTextCell?

I have properly setup an NSOutlineView with its data source and it's working great.
It's actually a basic File explorer, showing the folder structure of a particular path (folders, subfolders, etc). The subfolders are loaded on demand (when a folder item is expanded only then are its contents loaded, for speed reasons).
What I want is to know HOW I could EASILY add support to what I already have for the ImageAndTextCell class, so that I can put e.g. a folder/file icon next to each entry...
Any help is appreciated. (Please don't point me to documentation; I've studied almost all of it; what I need is advice by some who has done it, so that I just ADD to my existing code; without having to rewrite from scratch or totally change the logic...)
Thanks
Here's *My Code * (I had some trouble formatting it for SO... so I posted it on Snippet.MX)
Did what suggested and all the outline view items' names are suddenly NOT appearing...
Documentation is your friend but I understand sometime is so huge.
BTW
You need to set the ImageAndTextCell for your outline view, you can do it on you window controller init or awakeFromNib method or directly (if needed) on your NSOutlineView subclass.
On my project I've a NSOutlineView subclass as shown below
// myOutlineView subclass
- (void)awakeFromNib {
self.imageCell = [[ImageAndTextCell alloc] init];
[self.imageCell setEditable: NO];
NSTableColumn* leftColumn = [[self tableColumns] objectAtIndex:0];
[leftColumn setDataCell: self.imageCell];
}
Then you need to implement willDisplayCell delegate method where you set the image for your specific column as shown below (folder icon, file txt icon, jpg icon).
On the code shown below I get it from my singleton VDIconUtils but you can simply return a NSImage
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
[cell setDrawsBackground:NO];
if ([[tableColumn identifier] isEqualToString:#"mycellname"]) {
[cell setImage:[[VDIconUtils sharedIconUtils] iconForFolderStatus:fs :16 :[outlineView isItemExpanded:item]]];
}
}

passing data/objects/moc between viewcontrollers - best practice

I have a scenario that i suspect is very common, i've found various ideas in responses to other similar questions including setting up IBOutlets, passing NSmanagedobjects as properties and just using one view controller but swapping out the views but I'm uncertain as to which would be the best idea to implement for my solution.
I have an iOS/iphone app that has a typical model. UITabBarController containing multiple UINavigationControllers.
In one of the navigation controllers i have a viewcontroller with a tableView in it. This tableView is populated from fetched core Data entity is called 'tags'. When I set the tableView up with a UIBarButtonItem (Add) the add button needs to behave as follows: I need it to create a new 'tag' object for that entity and populate the object with some known data and then prompt the user to drill down 3 levels of classification (Region -> area -> topic) to reach a topic object. i then need to add that 'topic' object to a relationship of the original newly added 'tag' object (3 views further down the stack). - hope thats clear.
(there's lots of code I'm not sure which bits to present so i'm describing for now with the main snippet, by all means let me know any specific code you'd like to see.)
- (void)addTag {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:#"(untitled)" forKey:#"tagID"];
NSError *error;
if (![context save:&error])
NSLog(#"Error saving entity: %#", [error localizedDescription]);
ChooseRegion *aView = [[[ChooseRegion alloc] init] autorelease];
aView.theTag = newManagedObject;
[self.navigationController pushViewController:aView animated:YES];
}
You can see I've created the object, set one of it's properties a value, saved it and passed the object to the next view (chooseRegion) which is the first of 3 views. on each the didSelectRowAtIndexPath code passes the selected row and this 'tag' object to the next view, i'm not sure whether this is right because i'm not sure what to do when i get to the topic object on the 3rd view??? i'm lost.
Always more than one way to skin a cat but what approach should i be tackling this from. Should I be passing the 'tag' object forward and save it at the 3rd view or pass back the selected topic and save it at the original view which created the 'tag' object?
Many thanks in advance.
I would use notifications for this. Notifications are a simple way to decouple parts of the application, but still have them work together.
In your case, the moment the user selects the tag object in the 3rd view, I would send a notification as such:
[[NSNotificationCenter defaultCenter] postNotificationName:#"tagSelected" object:myTag];
Now, in the controller that has the "add" button, make it subscribe to that event:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleTagSelected:) name:#"tagSelected" object:nil];
Make sure you implement the handleTagSelected: method, and in that method you can get the tag object and close the drilldown view that you have open:
- (void)handleTagSelected:(NSNotification *)notification {
Tag *mytag = (Tag *)notification.object;
[self dismissModalViewControllerAnimated:YES];
}
Then you can do whatever you want with the tag.

Move focus to newly added record in an NSTableView

I am writing an application using Core Data to control a few NSTableViews. I have an add button that makes a new a record in the NSTableView. How do I make the focus move to the new record when this button is clicked so that I can immediately type its name? This is the same idea in iTunes where immediately after clicking the add playlist button the keyboard focus is moved to the new line so you can type the playlist's name.
Okay well first of all, if you haven't already got one, you need to create a controller class for your application. Add an outlet for the NSArrayController that your objects are stored in, and an outlet for the NSTableView that displays your objects, in the interface of your controller class.
IBOutlet NSArrayController *arrayController;
IBOutlet NSTableView *tableView;
Connect these outlets to the NSArrayController and the NSTableView in IB. Then you need to create an IBAction method that is called when your "Add" button is pressed; call it addButtonPressed: or something similar, declaring it in your controller class interface:
- (IBAction)addButtonPressed:(id)sender;
and also making it the target of your "Add" button in IB.
Now you need to implement this action in your controller class's implementation; this code assumes that the objects you have added to your array controller are NSStrings; if they are not, then replace the type of the new variable to whatever object type you are adding.
//Code is an adaptation of an excerpt from "Cocoa Programming for
//Mac OS X" by Aaron Hillegass
- (IBAction)addButtonPressed:(id)sender
{
//Try to end any editing that is taking place in the table view
NSWindow *w = [tableView window];
BOOL endEdit = [w makeFirstResponder:w];
if(!endEdit)
return;
//Create a new object to add to your NSTableView; replace NSString with
//whatever type the objects in your array controller are
NSString *new = [arrayController newObject];
//Add the object to your array controller
[arrayController addObject:new];
[new release];
//Rearrange the objects if there is a sort on any of the columns
[arrayController rearrangeObjects];
//Retrieve an array of the objects in your array controller and calculate
//which row your new object is in
NSArray *array = [arrayController arrangedObjects];
NSUInteger row = [array indexOfObjectIdenticalTo:new];
//Begin editing of the cell containing the new object
[tableView editColumn:0 row:row withEvent:nil select:YES];
}
This will then be called when you click the "Add" button, and the cell in the first column of the new row will start to be edited.
I believe an easier and more proper way to do it is by implementing it this way.
-(void)tableViewSelectionDidChange:(NSNotification *)notification {
NSLog(#"%s",__PRETTY_FUNCTION__);
NSTableView *tableView = [notification object];
NSInteger selectedRowIndex = [tableView selectedRow];
NSLog(#"%ld selected row", selectedRowIndex);
[tableView editColumn:0 row:selectedRowIndex withEvent:nil select:YES];
I.e.
Implement tableViewSelectionDidChange:(NSNotification *)notification
fetch the selected row index
Call editColumn:(NSInteger)column row:(NSInteger)row withEvent:(NSEvent *)theEvent select:(BOOL)select from there with the row index.
Important note: this solution will also trigger the editing when user will simply select a row. If you only want editing triggered when adding a new object, this is not for you.
Just create a separate #IBAction in your controller and invoke the NSArrayController.add method manually. After that you can select the row
#IBAction func addLink(_ sender: Any) {
// Get the current row count from your data source
let row = links.count
arrayController.add(sender)
DispatchQueue.main.async {
self.tableView.editColumn(0, row: row, with: nil, select: true)
}
}