passing data/objects/moc between viewcontrollers - best practice - objective-c

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.

Related

how to structure an objective-c view controller with tables and labels mixed

Dear Stack overflow geniuses,
I have already done a simple tableViewController page to my app, but now I need something a bit more advanced. I have a UI setup with links, labels, text boxes, and then a table of data at the end, so far my view looks like this:
I want to add a Table underneath the text box area and have it scrollable, how exactly do I structure my view controllers and how do I pass data from controller 1 --> controller 2?
I tried changing my current UIViewController to a tableViewController but I got some kind on inconsistency exception.
So what are my choices?
Create two controllers, one for the main screen, and one for the table
Somehow fix my issue and re-use the same UIViewController
Something else?
Here is the loading code I have so far for the main view controller, I guess where I am stuck, is if I create an outlet from my "tableview" to this UIView, how do I call code to "fill up" the table, without creating a separate standalone view controller.
Here is my code so far:
[self.objectManager getObjectsAtPath:#"/api/workorder/GetWorkOrderDetail"
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"It Worked");
_workOrders = mappingResult.array;
//paint screen
WorkOrderBig *mainWorkOrder = [_workOrders objectAtIndex:0];
self.lblWorkOrderId.text = mainWorkOrder.WorkOrderId;
self.lblPO.text = mainWorkOrder.PO;
self.lblSupervisor.text = mainWorkOrder.Supervisor;
self.lblPriority.text = mainWorkOrder.Priority;
self.lblStatus.text = mainWorkOrder.Status;
self.lblReceivedDate.text = [CommonUtils GetStringFromDate:mainWorkOrder.ReceivedDate];
self.lblDueDate.text = [CommonUtils GetStringFromDate:mainWorkOrder.DueDate];
WorkOrderProblem *problem = [mainWorkOrder.Problems objectAtIndex:0];
self.txtProblem.text = problem.Description;
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"What do you mean by 'there is no coffee?': %#", error);
}];
This is more of an architecture question and I can choose to do this the right way, so please suggest away at what might be the best way to proceed, Thanks!
It would not be unreasonable to make your current view controller conform to the UITableViewDataSource and UITableViewDelegate protocols. This would allow you to populate and respond to the table view. However, this is likely to grow your class's implementation quite a bit. If you do do this, it would be advisable to declare the conformity in a private extension at the top of your .m.
Another option would be to create a separate class that conforms to the two delegates. Then you would just maintain an instance of this class as a private member of your current controller. In your viewDidLoad: you would instantiation the class and set it as the delegate and data source of the table view.
Yet another option is to add a subclass of UITableViewController as a child controller of your current view controller.
objc.io did a great job of covering this in an article of their first issue: lighter view controllers: clean table view code.

Pushing a new instance of a table view controller object to the top of the stack while using the same nib file

I am learning how to program apps in ios. Anyone out there know of a recent tutorial that shows how to push a new instance of a table view controller object on top of the stack without the need to create and hookup new nib?
I found an old tutorial at iphone SDK Articles dated 3/2009. The website does not exist anymore as its domain expired. But the article, using a pre-packaged 2009 "Navigation-Based Application" in xcode, shows how to drill down a table using the same view controller rather than incorporate a new nib file. In the article, the view controller is identified as a "rootview" controller.
I have managed to modify the code to populate the data from the plist to a table cell using a UIViewController class with UITableViewDelegate and UI TableViewDataSource. This was not difficult to do. However, my problems begin when a cell is activated with the didSelectRowAtIndexPath method. The tutorial's code creates a new instance of the same table view controller class, updates the cell data and pushes it to the top of the stack to effect the "drill down." When I apply the same method to my controller class, xcode throws an error. Admittedly, my ignorance is the likely cause of the problem. That is why I am looking for help.
The tutorial code implementing the didSelectRowAtIndexPath:
//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];
//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:#"Children"];
if([Children count] == 0) {
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
} else {
//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:[NSBundle mainBundle]];
//Increment the Current View
rvController.CurrentLevel += 1;
//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:#"Title"];
//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];
rvController.tableDataSource = Children;
[rvController release];
}
}
It is hard to say for sure since you didn't post the actual error message.
One thing that may be an issue is that you pass Children to your new controller after pushing it to the stack. Therefore, some of the ViewController's view life cycle methods have probably already been called, and if you tried to do something on Children before it was set, that may crash the application.
Change it to :
rvController.tableDataSource = Children;
[self.navigationController pushViewController:rvController animated:YES];
Something else that may help : add an exception breakpoint in Xcode and set it to catch All exceptions on throw (I believe this is the default setting).
Should it crash because of something you wrote, the application will break on the actual line posing problem, which may help you understand the issue.
If this doesn't not help, as said before, be sure to post the actual error message.
I had to instantiate a navigation controller object in the app delegate. Self.navigationController does not work unless there is a navigation controller in play. I have since updated the code to work with storyboards by utilizing the UIStoryboard class.
I feel like I am slowly climbing Mt. Everest with every little step. It's painful for a new programmer (I make my living as an attorney) but somehow I seem to be moving forward.

NSTableView reload data from different controller

I am having problems updating the content of two tables, which are placed in two different tabs of a NSTabView and each controlled by a different controller (Controller1 and Controller3), from a third controller (Controller2).
I tried three different approaches:
Approach #1:
I created in Controller1 (the controller of the table invitesTableView) the method refreshTable :
- (void)refreshTable {
invites = //fill my mutable array
NSLog(#"%#",invites);
[self.invitesTableView reloadData];
NSLog(#"invite's table view updated");
}
which I later call from Controller2 doing :
Controller1 *controller1 = [[Controller1 alloc] init];
[controller1 refreshTable];
NSLog prints the content of the array invites correctly, but the table is not updated. I should say that, at application launch, I call the very same method in Controller1 and the content of the array invites is correctly loaded in the table.
Approach #2: When in Controller2 I do:
Controller1 *controller1 = [[Controller1 alloc] init];
controller1.invites = //fill my mutable array
NSLog(#"%#",controller1.invites);
[controller1.invitesTableView reloadData];
But again the content of the table is not updated, even if the NSLog shows the correct content.
Approach #3: As Controller1 controls the content of a NSTabViewItem, I integrated in Controller1 the method:
- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)inviteTab
and it works just fine, because I call the method refreshTable as soon as open the tab controlled by Controller1. The problem is that I have different tables in different tabs but I can set only one controller as the delegate of the NSTabView, so I can not update the table controlled by Controller3 in a different table.
I don't think there is an easy solution to approach #3, apart from merging Controller1 and Controller3, but I think I am just missing something with respect to the first two approaches.
Hope I explained myself clearly. Can anyone help? Thanks in advance!
You can use notifications/observer to reload tableview.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadTable:)
name:#"reloadNotification"
object:nil];
- (void)reloadTable:(NSNotification *)notif {
[self.tableName reloadData];
}
In your first tab use this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadNotification"
object:nil];

NSArrayController + Core Data + Bindings

I have one window containing an NSTableView listing a bunch of managed objects. I can right click a selection of those objects, and bring up an edit window to edit the properties of those objects (in theory).
Here's my code to bring up the window:
EditObjController *editObjController = [[EditObjController alloc] init];
// This line has to be first or the properties of editObjController are all nil.
[[editObjController window] makeKeyAndOrderFront:self];
[editobjController.arrayController setManagedObjectContext:[[NSApp delegate] managedObjectContext]];
NSArray *selection = [_objects objectsAtIndexes: [_objectsTableView selectedRowIndexes]];
[editObjController.arrayController setContent: selection];
My nib for the editing window has an NSArrayController in it. I have various textfields on the window, all of which are bound to the NSArrayController, controller key 'selection', with model key path=nameoffield.
The array controller is a valid object. No errors are reported.
All that comes up when the window appears is 'No selection' in all the textfields. What am I missing? I've googled extensively and I can't find a decent tutorial that explains how to actually edit records using Core Data and Bindings.
Your NSTableView and Edit Window should share the same array controller.

NSObjectController - setAutomaticallyPreparesContent:

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;
}