0
votes

I am trying to make a very simple iOS app that has two buttons: a +1 and a -1, and then on a separate tab, a label will display the total. I have been reading for the last hour about how to pass data between view controllers, but it doesn't make any sense to me. I am complete iOS newbie. Here is my code:

FirstViewController.h:

#import <UIKit/UIKit.h>

@interface FirstViewController : UIViewController
- (IBAction)takeOne:(id)sender;
- (IBAction)addOne:(id)sender;

@end

FirstviewController.m:

#import "FirstViewController.h"
#import "SecondViewController.h"

@interface FirstViewController ()

@end

@implementation FirstViewController

- (void)viewDidLoad
{
     [super viewDidLoad];
     // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
     [super didReceiveMemoryWarning];
     // Dispose of any resources that can be recreated.
}

- (IBAction)takeOne:(id)sender {
     SecondViewController.counter--;
     [label setText:[NSString stringWithFormat:@"%d", counter]];
}

- (IBAction)addOne:(id)sender {
     SecondViewController.counter++;
     [label setText:[NSString stringWithFormat:@"%d", counter]];
}
@end

SecondViewController.h

#import <UIKit/UIKit.h>

@interface SecondViewController : UIViewController
@property (nonatomic) int counter;
@property (weak, nonatomic) IBOutlet UILabel *label;

@end

SecondViewController.m

#import "SecondViewController.h"

@interface SecondViewController ()

@end

@implementation SecondViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

In FirstViewController.m, XCode is telling me errors that "Property 'counter' not found on object of type 'SecondViewController'" and that "Use of undeclared identifier 'label'".

Part of the reason I am confused is that there is a lot of information out there that seems to be for older versions of iOS. For example, I am very confused by the synthesize stuff.

Thanks for your help!

1
Why can't I just access the counter from SecondViewController?user2060214
Oh you definitely can just access it if you want. However, observers were made specifically for scenarios like this.rocky
@rocky You don't need observers unless you have something asynchronously updating the model while the second view view is showing. Generally you could just make sure that viewDidAppear for the second view controller retrieves the current model data, and that's all you need.Rob

1 Answers

0
votes

Your increment (SecondViewController.counter++) and decrement (SecondViewController.counter--) are referencing the class name, SecondViewController, but if you were going to do that, you must reference the particular instance of that class for which you want to increment/decrement.

Personally, though, I don't like a view controller trying to update something else in another view controller. A view controller should be updating only (a) its view; and (b) the model. The other view controller should then be observing changes on the model and reflecting those in the UI.

So, let's assume you have model class:

// Model.h

#import <Foundation/Foundation.h>

@interface Model : NSObject

@property (nonatomic) NSInteger counter;

@end

and

//  Model.m

#import "Model.h"

@implementation Model

- (id)init
{
    self = [super init];
    if (self) {
        _counter = 0;
    }
    return self;
}

@end

And instantiate a Model object. You could do this with singletons, but with a tab bar controller app, I might instantiate the Model in a UITabBarController subclass (and make sure to use this subclass in the storyboard):

//  TabBarController.h

#import <UIKit/UIKit.h>
#import "Model.h"

@interface TabBarController : UITabBarController

@property (nonatomic, strong) Model *model;

@end

and

//  TabBarController.m

#import "TabBarController.h"

@implementation TabBarController

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        _model = [[Model alloc] init];
    }
    return self;
}

@end

Then the first tab's view controller could have increment and decrement buttons which update the model:

//  FirstViewController.m

#import "FirstViewController.h"
#import "TabBarController.h"

@implementation FirstViewController

- (IBAction)didTouchUpInsideDecrementButton:(id)sender
{
    Model *model = [(TabBarController *)self.parentViewController model];

    model.counter--;
}

- (IBAction)didTouchUpInsideIncrementButton:(id)sender
{
    Model *model = [(TabBarController *)self.parentViewController model];

    model.counter++;
}
@end

And the second view controller could, when the view appears, retrieve the current value from the model and display it:

//  SecondViewController.m

#import "SecondViewController.h"
#import "TabBarController.h"

@implementation SecondViewController

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    Model *model = [(TabBarController *)self.parentViewController model];

    self.label.text = [NSString stringWithFormat:@"%d", model.counter];
}

@end

That's it.

Now, if that model could be changing while the second tab is visible, you'd also employ Key-Value-Observing (KVO), in which case your second view controller would be notified as the model changes. Frankly, the UI you describe doesn't necessitate that, but if you had something asynchronously updating the model while the second view controller was visible and you wanted to reflect these changes in the UI, then KVO is the solution:

//  SecondViewController.m

#import "SecondViewController.h"
#import "TabBarController.h"

@implementation SecondViewController

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    Model *model = [(TabBarController *)self.parentViewController model];

    self.label.text = [NSString stringWithFormat:@"%d", model.counter];

    // also observe any changes to the model while the second view controller is visible

    [model addObserver:self forKeyPath:@"counter" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    // if this view isn't visible, we don't need to observe the model any more

    Model *model = [(TabBarController *)self.parentViewController model];

    [model removeObserver:self forKeyPath:@"counter"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%s", __FUNCTION__);

    Model *model = [(TabBarController *)self.parentViewController model];

    self.label.text = [NSString stringWithFormat:@"%d", model.counter];
}

@end

Frankly, on the basis of what you've described thus far, KVO is probably not necessary, but it's useful in more complicated apps, so I include it for your future reference.