0

I'm working with a model class and passing it back and forth between 3 controllers. I'm having trouble allowing customers to make selections on one controller and have them displayed in string form within a UILabel on another controller.

Each time the customer is taken to the controller where they make the selection and returned back to the previous controller, their selection is shown in string form as expected but previous selections are cleared. So I'm only able to show the selection for one category at a time.

Please read on for further details:

Controller 1 - The page that displays a collection view of clothing items

Controller 2 - A page the customer is taken to when tapping refine

Controller 3 - The page the customer selects the ways they wish to refine

In Controller 2 I display a list of different ways to refine search results by:

enter image description here

In Controller 3 the customer can make the actual selection:

enter image description here

I'm working with a class file which acts as my model. It's class is called VAGRefineModel. This class is first initialised in controller 1 and during the prepareForSegue method set as the value of a property of controller 2 called refineModel.

In controller 2 I pass a copy of the model to controller 3 using the prepareForSegue method again:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[[sender titleLabel] text] isEqualToString:@"Gender"]) {
        VAGRefineGenderViewController *vc = [segue destinationViewController];
        [vc setDelegate:self];
        [vc setRefineModel:[[self refineModel] copy]];
    }
}

Once customer lands on controller 3 they check what ever boxes are appropriate. Then by tapping the done button or tapping back the details are saved in the copy of the model. I use NSUserDefaults to track the status of my switches. This isn't important right now. The issue I'm having is after the customer returns to controller 2 their checkbox selection is displayed underneath the refine by option they chose. In my example you see underneath gender my selection is shown.

Ok this is fine but now when I tap on let's say brand to choose some more ways to refine my results then return back to controller 2 selection for gender is cleared and blank. I assume because the prepareForSegue method creates a new copy of my model to set as controller 3's refineModel property value.

I need a way to temporarily keep track of the selections made before the apply button is tapped. The apply button saves my copy model as the original models value. This is in place so that if a customer decides they don't wish to apply the selections they have made and return to controller 1 without tapping apply the original or previous selections if any, are returned.

More info:

I need a clear way to keep hold of all the data selected in controller 3. The way my app is set up it just gets reset.

Here is a delegate method from controller 3 that the model from controller 3 is passed to controller to in.

- (void)didTapDoneButtonWithRefineModel:(VAGRefineModel *)refineModel
{
    [self setRefineModel: refineModel];
}

I take the model passed over from controller 3 and set controller 2's property value to that. However this is the original I am setting. Nothing is saved unless apply button is tapped.

I have a UILabel under gender, brand, product type etc. This get's set upon returning to controller 2 from controller 3 depending on what's been selected.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [[self chosenGenders] setText:[[self refineModel] selectedGenderString]];
    [[self chosenBrands] setText:[[self refineModel] selectedBrandString]];
}

Question:

I need a way to keep those labels set once they have been set. If I return to controller 1 by tapping the back button everything is cleared. If apply button is tapped instead the data is stored in the original model.

How do I keep hold of the selection made in controller 3 no matter what?

Copy method as requested:

#import "VAGRefineModel.h"

@implementation VAGRefineModel
{
    VAGRefineModel *_object;
}

-(id)copyWithZone:(NSZone *)zone
{

    VAGRefineModel *modelCopy = [[VAGRefineModel alloc] init];
    modelCopy->_object = [_object copyWithZone: zone];
    return modelCopy;
}
LondonGuy
  • 10,778
  • 11
  • 79
  • 151
  • 1
    Why not use a singleton to keep all your data in one place rather than passing it all over the place? – Stonz2 May 22 '14 at 20:01
  • 1
    Why `copy`? You are aware it's causing issues... why are you doing it? – stevesliva May 22 '14 at 20:10
  • 1
    @Stonz2 would like to avoid singletons for now. – LondonGuy May 22 '14 at 20:12
  • @stevesliva if I don't work with a copy I get a whole bunch of other problems. As explained [HERE](http://stackoverflow.com/questions/23795255/im-having-trouble-dynamically-updating-a-uilabel-in-one-controller-from-another/23795639#23795639) – LondonGuy May 22 '14 at 20:14
  • 2
    Ok. `copy` makes cancel simple ... but like the singleton suggestions here it's a single design pattern applied as a band-aid to the app. I don't see any reason you should be creating a deep copy of the model object, nor do you need a singleton. But that's just me. What you need is to start figuring out your VC flow on a whiteboard. Maybe keep a copy that is only used for cancel? – stevesliva May 22 '14 at 20:28
  • I've done many whiteboards and when I get down to coding things don't seem to work out. Been at this for over a week and I literally mess up when it comes to the labels I mentioned in the question. I guess more experimentation is needed. I believe I would have been in deeper trouble than I am now if I started out with a singleton. Passing the data back and forth has benefitted me in many ways. – LondonGuy May 22 '14 at 20:35
  • 1
    I'd like to see your `-copy` method. It sounds like it's not really copying everything. – Caleb May 22 '14 at 20:37
  • @Caleb added the copy code that's present in my model classes implementation file – LondonGuy May 22 '14 at 20:39
  • 1
    What's in a `VAGRefineModel`? It looks like just a pointer to another model, like a linked list without any values. Anyway, I think you should look at whether the `-copy` method is doing the right thing. If you make a copy of the model and set that copy as your new model without involving any other controller, are the previous setting preserved? – Caleb May 22 '14 at 20:46
  • All my properties and methods for the refining section of my app are stored in the VAGRefineModel file/class. Tried what you stated and previous settings are preserved. – LondonGuy May 22 '14 at 21:03
  • The first three lines after your `@implementation VAGRefineModel` don't make any sense there. Is that supposed to be an `@interface` block? And maybe I'm missing something, but if `_object` is a `VAGRefineModel*`, having your `-copyWithZone:` call `[_object copyWithZone: zone]` seems like it'd lead to infinite recursion. Is that your actual code, or are you trying to simplify it for the question? – Caleb May 22 '14 at 21:27
  • @Caleb It's the exact code. Could this be my issue? – LondonGuy May 22 '14 at 21:38
  • 1
    @Caleb the problem was with my -copyWithZone method as you said. I misunderstood how to correctly use it. I had to manually set the values of the copy to the properties of the original. Doing this solved all the issues I was experiencing. – LondonGuy May 22 '14 at 22:33
  • Thought so... glad it worked out. – Caleb May 22 '14 at 22:47

2 Answers2

1

As spotted by caleb the issue was with the copyWithZone method in my models implementation file. I wasn't setting the values of the copied models properties.

Editing the code show above to this solved my problem:

-(id)copyWithZone:(NSZone *)zone
{
    VAGRefineModel *modelCopy = [[VAGRefineModel allocWithZone: zone] init];

    [modelCopy setSelectedGenderString:[self selectedGenderString]];
    [modelCopy setSelectedBrandString:[self selectedBrandString]];
    // etc

    return modelCopy;
}

Also I've always avoided singletons as I believe as my app grows and becomes more complex they'll work against me. Using segue methods and delegates to pass data back and forth between controllers feels like I have more flexibility and also feels like the right thing to do.

LondonGuy
  • 10,778
  • 11
  • 79
  • 151
0

As @Stonz2 proposed on the comment, you should make a singleton instance of the model, like an application model, to keep selections over the whole app and not passing a model instance between controllers.

Here's an example for a singleton implementation:

Singleton instance

There, add properties for

selectedGenderString 
selectedBrandString

and simply use this for setting:

[[VAGRefineModel sharedInstance] setSelectedGenderString:@"Some selected, other"];

and this for getting:

[[VAGRefineModel sharedInstance] selectedGenderString];

This is pure MVC philosophy! Hope it works for you.

Floydian
  • 249
  • 2
  • 17
  • I have had many people tell me avoid them in this instance then a few people suggest them. I've read many articles against them and this is the reason I initially avoided using one. The way I'm passing my model object round now is using segue methods and delegates. – LondonGuy May 22 '14 at 20:23
  • 1
    Taking in mind that ObjectiveC and iOS development structure is mostly made for using MVC, the Models (used as singleton or not) have to worry about that trying to not involve Views or Controllers (segues) for that. Said that, the ApplicationDelegate itself is a singleton instance, so you could in the worst case, create an instance (simple strong instance) for the model there and access to it by [UIApplication sharedApplication].delegate (read this http://www.hollance.com/2012/02/dont-abuse-the-app-delegate/) – Floydian May 22 '14 at 20:56
  • Why do you feel that a single global object is necessary here? How is that better than simply passing an object containing the necessary information between the two view controllers? – Caleb May 22 '14 at 21:22
  • It makes a lot of sense to have a Model Object, representing the "state" of your application and keeping the responsibilities of the business logic there and the presentation logic just to the controllers. Besides, if in some case, your app needs to save the current state (due to memory warnings or just resigning active), the only object you need to store/retrieve is the App Model. – Floydian May 22 '14 at 21:33
  • 1
    Of course it makes sense to have a model object, but that doesn't explain why the model should be a globally shared singleton. Singleton adds the unnecessary constraint that the class can be instantiated no more than once, and it prevents you from restricting access to the model to just those objects that need it. Global state, such as you have with a singleton, leads to code that's harder to debug and harder to reuse. – Caleb May 22 '14 at 22:47
  • The App Model, in theory, should live as the app does, so a singleton fits there. The restriction to access or not a model should be made from the structure/architecture point of view and not from the development one. If you think about the model being instantiated more than once, you leave the problem of the state of the model to the controllers passing it from one to other and leading to inconsistency between the info on the views, even more with the Object Ownerships modifiers of ObjC. Anyway, trade-offs have to be made with this decision. – Floydian May 23 '14 at 16:07