IOS MVC pattern and data controller for manipulating NSMutableArray - ios

I have:
a class called Care
an NSMutableArray that is called masterCareList
masterView, detailView
careDataController for adding Cares to List and count number of objects.
I want to count my objects in MasterView and I want to add an object in detailView. In both cases using the careDataController (not rewind segue or passing data between views) and to the same masterCareList.
How do I use the same dataController and therefore the same NSMutable array in the best (MVC) way?

You could create your careDataController on initial setup of your app and access the careDataController through a property of MasterView. This would enable you to encapsulate counting and other operations on your data separate from your view controller.
a very high level example somewhere where you create MasterView...AppDelegate maybe...
//Appdelegate.m file
MasterView *mv = [[MasterView alloc] init];
DataController *careDataController = [[DataController alloc] init];
mc.model = careDataController; //model is a property within MasterView that you create
[self presentViewController:mv animated:YES completion:nil];
now you could essentially in MasterView present the DetailView after creating it and setting its model property you create to MasterViews model property you set above.
//some procedure in MasterView.m
DetailView *dv = [[DetailView alloc] init];
dv.model = self.model; //self.model you set above
[self presentViewController:dv animated:YES completion:nil];
now once in the DetailView you can add care objects to the original careDataController you initially created at the start of your app...
//somewhere in DetailView.m
Care *careObject = [[Care alloc] init];
[self.model addObject:careObject];
when you dismiss the DetailView after you are done adding an object your careDataController (model property) in MasterView will now have the new care object you added in the DetailView.
EDIT:
I see your new comment, in a way its essentially what I just wrote above which I do not feel is incorrect. Another thing you could do is instead of passing it like above, have MasterView be a delegate for DetailView. When you add a Care object save that object state in DetailView and pass it back using a the protocol you defined in DetailView. You might have a method in the protocol like below that MasterView implements...
-(void)detailView:(DetailView *)dv didInsert:(Care*)careObject
{
[self.model addObject:careObject];
}

Related

How to pass data from second view to first view without using segues?

I want to pass string data from second view to first view.
My first view contains a UITableView with 4 rows. If user taps on 1st row, programmatically, I push a new view controller.
For example:
if(indexPath.row == 0)
{
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"NewView"];
[self.navigationController pushViewController:controller animated:YES];
}
My second view is a tableviewController, wherein user can select any one option from second view which should get passed to first view.
There is no back button as special; since I have used Navigation controller I get navigation back button.
So when user presses navigation back button, data from second view should get passed to first view.
There is multiple ways for doing that.
You can use NSUserDefaults for storing data.
You can use Delegate Methods.
I would recommend against using NSUserDefaults as you do not need persistent behavior, you want to handle messaging between 2 objects. NSUserDefaults are designed to support customization based on a user's preferences. See the snippet from the documentation below.
I would recommend delegation as it is a design pattern used throughout Apple's code and would be very beneficial for you to know well!
The NSUserDefaults class provides a programmatic interface for
interacting with the defaults system. The defaults system allows an
application to customize its behavior to match a user’s preferences.
For example, you can allow users to determine what units of
measurement your application displays or how often documents are
automatically saved. Applications record such preferences by assigning
values to a set of parameters in a user’s defaults database. The
parameters are referred to as defaults since they’re commonly used to
determine an application’s default state at startup or the way it acts
by default.
There are a number of ways of going this.
Create a weak instance variable in the second view that references the first view and use that to get the data to the first view: weak ViewController* firstView. Remember to add an instance variable to capture the data to firstView.
This works but introduces tight coupling between the views. They are now dependent on each other to meet the required functionality.
if (indexPath == 0) {
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"NewView"];
MyController *myController = (MyController *)controller;
myController.firstView = self;
[self.navigationController pushViewController:controller animated:YES];
}
Use a block as a completion handler the second view can use call once the data in question has been selected. Define a block on the secondView. Now in your if statement set the completion handler block o the secondView. Now when the data is selected call the completion handler block.
Use the delegate pattern and define a delegate protocol and delegate property for secondView. The firstView will implement the delegate protocol. So when the data is collected you would call in the secondView.
[delegate SecondViewDidCollectData:myData];
Property on SecondView
#property (weak, nonatomic) id<SecondViewDelegate> delegate;
Delegate Protocol defined in SecondView header.
// define the protocol for the delegate
#protocol SecondViewDelegate
// define protocol functions that can be used in any class using this delegate
-(void)SecondViewDidCollectData:(NSData *)data;
#end
You can achieve this using following ways;
You can use NSUserDefaults for storing data.
You can pass the data like below :
yourViewController *vc = [[YourViewController alloc]init];
vc.xyzString = "yourStringToPass";
[self.navigationController popToViewController: vc animated:YES];
Hope this will help.
Add below Code in your tableView didSelectRowAtIndexPath Method.
[self.navigationController.viewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if([obj isKindOfClass:[firstViewController class]]) {
firstViewController *objfirstViewController = obj;
objfirstViewController.xyzString = "yourString";
[self.navigationController popToViewController: objfirstViewController animated:YES];
*stop = YES;
}
}];

Custom object of same type added multiple times inside for loop

I am allocating custom object (viewcontroller in this case) inside for- loop. And everything seems to work fine. But when I tap on the button of first custom object of viewcontroller, the application crashes.
It is because the instance for the custom object is not retained. Although it works fine for the last added object.
Please advice.
dispatch_async(dispatch_get_main_queue(), ^{
NSInteger index = 0;
for (TestStep *obj_Teststep in objTestSuite.testSteps) {
TestStepView * obj_TestStepView = [[TestStepView alloc] initWithNibName:#"TestStepView" bundle:[NSBundle mainBundle]];
obj_TestStepView.testStep = obj_Teststep;
obj_TestStepView.delegate = self;
DMPaletteSectionView *sectionView = [[DMPaletteSectionView alloc] initWithContentView:obj_TestStepView.view andTitle:[NSString stringWithFormat:#"Test Step %# - %#",obj_Teststep.executionOrder,obj_Teststep.apiCallPath] initialState:DMPaletteStateCollapsed withAction:YES andIndex:index];
sectionView.layer.backgroundColor = [NSColor redColor].CGColor;
[sectionArray addObject:sectionView];
index++;
}
[sectionArray addObject:[[DMPaletteSectionView alloc] initWithContentView:self.addNewTestStepView andTitle:#"Add Test Step" initialState:DMPaletteStateExpanded withAction:NO andIndex:0]];
container.sectionViews = sectionArray;
for (int i =0; i<container.sectionViews.count; i++) {
DMPaletteSectionView *dmobj = [container.sectionViews objectAtIndex:i];
dmobj.delegate = self;
}
});
As #trojanfoe says, your design is faulty. You can't create a view controller and add it's view to another view controller without maintaining a strong reference to the view controller.
You create a bunch of TestStepView objects (which I assume are view CONTROLLERS?) Then you pass the view of those objects to a DMPaletteSectionView, but never retain a strong reference to the TestStepView object. That won't work.
When you add a view controller's view to another view controller you should use the parent/child view controller support that was added to iOS (in iOS 5, if I remember correctly.) Do a search in the Xcode docs in the UIViewController class reference for the words "parent" and "child". There are a family of methods that let you set that up.
You would need to make your TestStepView (view controller?) a child of the DMPaletteSectionView (view controller?)
BTW, stop calling view controllers views, both in your question and in your code. View objects and view controller objects are totally different, and you will confuse both yourself and your readers by calling view controllers views.
I use the abbreviation VC for view controllers in my code to keep my class names shorter, while keeping it clear that they are view controllers, not views.
You are allocating the view controllers and then effectively throwing them away as ARC will deallocate them when they go out-of-scope:
for (TestStep *obj_Teststep in objTestSuite.testSteps) {
TestStepView * obj_TestStepView = [[TestStepView alloc] initWithNibName:#"TestStepView"
bundle:[NSBundle mainBundle]];
// ...
// ARC will deallocate obj_TestStepView here
}
This isn't how you are supposed to use view controllers; they are expected to be presented (normally one-at-a-time), so what you are doing is undefined.

Add 2 or more UIViewController class to a Navigation Stack

I have a reusable UIViewController class which has a tableview in it, say Class T. I have a list of things in this to be displayed.
Now when I press on one of the cells I create a new instance (alloc init) of this class and push it to the navigation stack and display new data in the same class T.
The problem comes when I pop the controller it goes to the first instance if the same class but the tableview displays the data which was displayed in the second instance.
I am using an XIB for Class T and not storyboard and segues.
Please help me resolve this issue.
Thanks,
[EDIT - I] Initializing
T *controller = [T alloc] initWithNibName:#"T" bundle:nil];
[self.navigationController pushViewController:controller animation:YES];
Your issue is clearly based on the missing update of the data used in the table view's datasource. I suggest that each controller gets its own object, array or fetched results controller. You can then do something like this:
T *controller = [T alloc] initWithNibName:#"T" bundle:nil];
controller.dataArray = ... // populate the data source
Alternatively, using the same datasource, you have to make sure to check what should be displayed. Maybe you can give your table view controller class a type property that gets checked when displaying the data cell.
if (self.type == HierarchyFirstLevel) {
cell.textLabel.text = ... // populate for first level
}
else if (self.type == HierarchySecondLevel) {
cell.textLabel.text = ... // something else
}
// etc.

instantiateViewControllerWithIdentifier and pass data

I am using Storyboard in my app and I want to pass data from one view to another view.
Instead of using segues I am using instantiateViewControllerWithIdentifier. In this case I am instantiate from my first TableViewController to a NavigationController which has a second TableViewController attached because I need the navigation in the second TableViewController. Now I want to pass data from my first TableviewController, depending which row was clicked, to my second TableviewController. In this case newTopViewController would be my NavigationController but my problem is now how to pass data from firstTableViewController to the secondTableviewController.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [NSString stringWithFormat:#"%#Top", [menuArray objectAtIndex:indexPath.row]];
UIViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
}
If you instantiate a navigationController, you can use the viewControllers property to get the inner viewController of the navigation controller.
Something like this:
UINavigationController *navigationController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
MBFancyViewController *viewController = navigationController.viewControllers[0];
// setup "inner" view controller
viewController.foo = bar;
[self presentViewController:navigationController animated:YES completion:nil];
newTopViewController.anyVariableToShow= anyVariableToSend;
I do this pretty often on a few of my apps...
//Create new VC
CookViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CookVC"];
//Set recipe
[detailViewController setRecipe:recipe];
//Pop over VC (can be pushed with a nav controller)
[self presentPopupViewController:detailViewController animationType:MJPopupViewAnimationFade];
If you aren't using a navigation controller or segues, then I think you need to reconsider your app design.
Actually it's not just a data pass problem as this is a program control and data transfer question together.
Even you would have to rethink about your app's concept, as you'd like to use storyboard without the meaning of storyboard, it's up to you and I hope you have good reason to do what you do.
So when you decided not to use segue you lost the new and comfortable way of instantiating a new controller and transferring data with it and you have to do the transfer of control and the data in two distinct steps. When you instantiate another scene in storyboard (like you do with instantiateViewControllerWithIdentifier:) you just instantiated a new controller and transferred the control but not the data. Just think about it as you instantiated a new controller from a xib in an old way (so you have to use initWithCoder: or awakeFromNib in the second view controller as the storyboard will not call initWithName:bundle:), but did not do anything more.
So you will have a new controller (it named in the identity part of the second storyboard) which is hanging in the universe without any relationship or connection with anything else (as the storyboard picture illustrates it nicely) and you could do with it what you'd like.
So you'd like to do something and you need data from the previous storyboard (ViewController). What you need is making available those data to the second storyboard(ViewController), and as you know there are lot of solution for this which were available long time before even storyboard is existed.
So regarding your code, the "data transfer" is depending on your design, whether the two controllers are subclasses of each other or whatsoever...
If you don't like to deal with subclassing and like to decoupling them as much as possible, the best way just make a property of your data in the first controller and refer to them from the second (after importing the first's .h file) and just refer to it in it's viewDidLoad or in initWithCoder: or anywhere where you need them, as
secondViewControllerdata = firstViewControllerdata.thatDataProperty
Of course you can do the same in reverse and make a property of the second controller and refer to it in your first view controller.
You can define some parameter in UIViewController to receive data:
#property (assign) int param1;
#property (retain) NSMutableArray *param2;
and use below to pass the data:
[newTopViewController setParam1:XX];
[newTopViewController setParam2:XX];

Cannot pass messages between main view controller and popover view

I'm seem unable to get any kind of communication going between my Main View Controller and a Table View Controller which is being displayed inside a Popover View (iPad).
I'm setting up the Table View inside a Navigation Controller in the usual way:
// create popover
if (self.popoverController == nil) {
filesViewController = [[[MyTableViewController alloc] initWithFiles:fileList] autorelease];
UINavigationController *navCtrl = [[[UINavigationController alloc] initWithRootViewController:filesViewController] autorelease];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:navCtrl];
self.popoverController.delegate = self;
// resize popover
self.popoverController.popoverContentSize = CGSizeMake(320.0, 44 + [fileList count] * 44);
}
Everything is working fine, and I'm passing an array of file names (fileList) into the Table View, which is held in the Table View as an array called listOfFiles. The Table View displays the filenames, and when one is selected by the user I want to pass that filename back to the Main View Controller. However, I cannot get any communication going back from the Table View's didSelectRowAtIndexPath method to the Main VC. I've tried all sorts of outlets going in various directions, and I've tried creating a new object in didSelectRowAtIndexPath to handle the filename coming from the Table View. I can pass the filename out to the new object, but when I try to send that into the Main VC it is null again. Everything I send to my Main VC while that popover is active comes up as null.
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
NSLog(#"%#", handler.addressForImageFile);
self.popoverController = nil;
[self.popoverController release];
}
Is there some reason why my Main VC won't get anything but null objects from my Table View? I've spent days trying so many different things. I feel like there's some fundamental gap in my knowledge of how popovers work. Surely there is a simple way to send a string back to my Main VC when it is selected from the Table View?
Thanks so much for any help!
There's propably a much better way to do this, but depending on the goal of passing the string, one way could be to use NSUserDefaults.

Resources