0

So I have 2 different table views that use the same array (the array is originally created in the Role table view, the below one). How can I connect those two? (Usually I use prepareForSegue to pass the data but since there is no segue, I'm not sure how can I do this)

enter image description here

EDIT 1: Add the location of the array.

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
Tree Nguyen
  • 1,198
  • 1
  • 14
  • 37
  • Where do you get this array from? – fpg1503 Feb 01 '15 at 14:28
  • @fpg1503 added the array location :). Thanks for reminding me :) – Tree Nguyen Feb 01 '15 at 14:36
  • If you use tab bar controller, you should have unrelated app components displayed on separate tabs...think about using navigation controller and changing your view controller hierarchy – user3435374 Feb 01 '15 at 14:43
  • @user3435374 Thanks for your opinion. I am pretty sure of what I'm doing right now :) – Tree Nguyen Feb 01 '15 at 14:47
  • Although some might disagree, you could try a singleton class as a "global data object". – CaptJak Feb 01 '15 at 14:49
  • @CaptJak that's one way, but I was taught to avoid it as much as possible. So maybe I will wait until there is a better way to do it or I will make it global :-? – Tree Nguyen Feb 01 '15 at 14:53
  • There definitely are appropriate uses for singletons. Since singletons, as a design pattern can be abused to "work" in so many ways, they are generally abused. Don't think that you should never use them, just be sure you are using them for good, not evil. In your current case though: http://programmers.stackexchange.com/questions/40373/so-singletons-are-bad-then-what – CaptJak Feb 01 '15 at 14:59
  • And definitely passing objects between view controllers is NOT an appropriate use case for singletons, as it f*cks up the memory management. ARC won't work as the singleton can't know when data is not needed anymore. Also it isn't wise that the "receiving view controller" takes care of the memory management, as it hides such details to other classes that then also might delete it,… – vikingosegundo Feb 01 '15 at 21:52

3 Answers3

1

What is a Model and why you need it

In most of the cases it's useless to pass data around if you don't have a Data Model. You can store your data using a technique called Data Persistence.

An example of a pattern you could use is MVC. MVC or model-view controlelr is an software pattern widely using when making iOS Apps. In this architectural pattern your Controllers are a bridge between your View and your Model.

In this specific scenario both UITableViewControllers would use the same Model but they would display this data differently.

Persisting your Model

There are several ways to do that, the way I like the most is a little framework called CoreData, you can see this question for some reference on that.

You can also refer to this question to see the use of Singletons. But keep in mind that singletons alone do not persist the data. You'll have to add some sort of mechanism if you want the data to remain there between app sessions.

Persisting user preferences

The simplest way to store small chunks of data is using NSUserDefaults (but it's only meant to store defaults):

Let's assume you have an array

NSArray* testArray = @[@"first", @"second", @"third"];

You can set it to a key by using

[[NSUserDefaults standardUserDefaults] setObject:testArray forKey:@"myArray"];

You can sync NSUserDefaults using

[[NSUserDefaults standardUserDefaults] synchronize];

Then, anywhere in your app you can read it doing

[[NSUserDefaults standardUserDefaults] objectForKey:@"myArray"]

Passing data through the app

On the other hand you have to pass your data around somehow. To do so you can use formal protocols, specifically delegates. As per the Apple documentation:

In a delegate-based model, the view controller defines a protocol for its delegate to implement. The protocol defines methods that are called by the view controller in response to specific actions, such as taps in a Done button. The delegate is then responsible for implementing these methods. For example, when a presented view controller finishes its task, it sends a message to the presenting view controller and that controller dismisses it.

Using delegation to manage interactions with other app objects has key advantages over other techniques:

  • The delegate object has the opportunity to validate or incorporate changes from the view controller.

  • The use of a delegate promotes better encapsulation because the view controller does not have to know anything about the class of the delegate. This enables you to reuse that view controller in other parts of your app.

For more information on passing data through view controllers (the main point of this question) take a look at this SO answer.

Community
  • 1
  • 1
fpg1503
  • 7,492
  • 6
  • 29
  • 49
  • `NSUserDefaults` shouldn't be used to store data or global variables. It's for user defaults. – CaptJak Feb 01 '15 at 15:04
  • I know, that's why I mentioned CoreData – fpg1503 Feb 01 '15 at 15:06
  • 2
    True, but I had to say it. Since it would work for the OPs situation, it might be what he ends up using to store his data (which doesn't seem to be defaults), unless otherwise warned. – CaptJak Feb 01 '15 at 15:10
  • Not really related to my problem: How can you use the synchronize for NSUserDefaults? Since I didn't see any parameter with it by any sorts. – Tree Nguyen Feb 01 '15 at 15:35
  • 1
    Using singletons is actually great :). I have just checked it out and totally understand the proper way to do it :). Thanks you two :) – Tree Nguyen Feb 01 '15 at 15:43
  • 1
    This method synchronizes your default to the file, no parameters are needed, it's just an explicit sync request. The system auto-syncs every now and then. – fpg1503 Feb 01 '15 at 17:06
  • Could the downvoter please explain it so I can improve my answer? – fpg1503 Feb 01 '15 at 17:07
  • @fpg1503 is wrong: callbacks and delegates are always to be use for passing data, not only for small chunks. actually his solution might only work with small chunks, as data is really copied and saved to disk. – vikingosegundo Feb 01 '15 at 21:54
  • see the edit of my answer for an example how to pass any object through the app without the need of persisting it or using singletons or other forms of globals. – vikingosegundo Feb 02 '15 at 00:44
  • I never said you couldn't, I know it's possible, all I was saying is that it's probably better to have a model if you plan to deal with large amounts of data instead of passing an array around. Your answer is pretty elucidative and complete, though. Thanks for taking to time :) I was mainly focusing on data persistence, you're totally right, I missed the point of the question, my bad. – fpg1503 Feb 02 '15 at 01:43
  • BTW, I forget to mention: this solution will only work for custom model objects if special preparation is done: for core data the classes must derive from NSManagedObject, for NSUserDefaults they must implement the NSCoding protocol. – vikingosegundo Feb 02 '15 at 18:04
1

You should never use data persistence just to pass data through the app. Neither user defaults nor core data.
Also using singletons is not good choice. All will mess up your memory.

Instead use call backs — either as delegates or blocks.

Or use unwind segues.

I explain delegates and unwind segues here: Passing row selection between view controllers

this example passes index paths, as it is appropriate in that situation, but the passed object might be of any type or size, as only pointers are passes.

if you use the NSUserDefaults on the other side, data is copied and written to the disk — there for data is copied and slowly processed — without any use.


I created a sample app how to pass data from one view controller to another view controller in another tab bar branch.

storyboard click to enlarge

TabBarController

We need to intercept the section of view controllers to set up some callback mechanism. In this case I am using blocks, but delegate would work as-well.

UITabController has a purely optional delegate. I create a subclass of UITabBarController to serv as it's own delegate, but actually a separate delegate should work in the same way.

#import "GameTabBarController.h"
#import "RoleViewController.h"

@interface GameTabBarController () <UITabBarControllerDelegate>
@end

@implementation GameTabBarController

-(void)viewDidLoad
{
    [super viewDidLoad];
    self.delegate = self;
}

-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
    if ([viewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController *navController = (UINavigationController *)viewController;
        if ([navController.topViewController isKindOfClass:[RoleViewController class]]) {
            RoleViewController *rvc = (RoleViewController *)[navController topViewController];
            [rvc setSelectedRole:^(Role *role) {
                UIViewController *viewController = self.viewControllers[0];
                [viewController setValue:role forKey:@"role"];
                [self setSelectedIndex:0];
            }];
        }
    }
    return YES;
}

@end

I set the initial tab bar controller to this sub class

Role, RoleDatasource and RoleViewController

The RoleViewController displays a list of Roles, but the datasource and delegate for it's table view are a separate class that I add to the role view controller scene in the storyboard, where i also were it up.

Role
@interface Role : NSObject
@property (nonatomic,copy, readonly) NSString *name;

-(instancetype)initWithName:(NSString *)name;
@end

#import "Role.h"

@interface Role ()
@property (nonatomic,copy) NSString *name;
@end

@implementation Role
- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    if (self) {
        _name = name;
    }
    return self;
}
@end
RoleDatasource
#import <UIKit/UIKit.h>
@class Role;
@interface RoleDatasource : NSObject <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, copy) void(^roleSelector)(Role *role);
@end

#import "RoleDatasource.h"
#import "Role.h"

@interface RoleDatasource ()
@property (nonatomic,strong) NSArray *roles;
@end

@implementation RoleDatasource
- (instancetype)init
{
    self = [super init];
    if (self) {
        _roles = @[[[Role alloc] initWithName:@"Magician"], [[Role alloc] initWithName:@"Soldier"], [[Role alloc] initWithName:@"Maid"]];
    }
    return self;
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.roles.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *identifier = @"RoleCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
    cell.textLabel.text = [self.roles[indexPath.row] name];
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.roleSelector(self.roles[indexPath.row]);
}

@end
RoleViewController
#import <UIKit/UIKit.h>
@class Role;
@interface RoleViewController : UIViewController
@property (nonatomic, copy) void(^selectedRole)(Role *role);
@end

#import "RoleViewController.h"

#import "RoleDatasource.h"

@interface RoleViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end

@implementation RoleViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    RoleDatasource *roleDataSource = (RoleDatasource *)[self.tableView dataSource];
    [roleDataSource setRoleSelector:^(Role *role) {
        self.selectedRole(role);
    }];
}

@end

PlayViewController

As soon as a role is selected on the role view controller we want to tell our tab bar controller to switch to the game view controller and show the selected role there, see the code for the tab bar controller.
The GameViewController is just a simple view controller subclass that has a property to hold a role and if a role is set, it will displays it name.


#import <UIKit/UIKit.h>
@class Role;

@interface PlayViewController : UIViewController
@property (nonatomic, strong) Role *role;

@end

#import "PlayViewController.h"
#import "Role.h"

@interface PlayViewController ()
@property (weak, nonatomic) IBOutlet UILabel *roleNameLabel;

@end

@implementation PlayViewController

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.roleNameLabel.text = (self.role) ? self.role.name : self.roleNameLabel.text;
}

@end

You'll find an example on github.

Community
  • 1
  • 1
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • I got your point and I agree with you but I don’t think his issue was directly concerning passing data from one VC to another, it was mostly rooted in the lack of a Model. I honestly think that having this two tableviews passing an array back an forth to be used as a model is not a good idea. IMHO in this specific scenario using a class to access the model is the best option, whether it’s a CoreData DAO, a singleton writing data somewhere or whatever he thinks suits his needs better. My point is he’s **not** trying to pass just an NSIndexPath, we wanted to transfer his whole model. – fpg1503 Feb 01 '15 at 19:11
  • An index path is just another object. pass what ever suits best. – vikingosegundo Feb 01 '15 at 19:35
  • You're answer really gets to the core of the question (passing data), pretty good :) – fpg1503 Feb 02 '15 at 01:44
  • 1. Thanks for answering my question @vikingosegundo. 2.Since I'm new to obj-C, would you mind explain for me these things a little bit clearer? a. I don't get the idea of GameTabBarController at all (I understand slightly about the use of delegate, never used it). b. I don't understand why you have RoleDataResources does the work of UITableView? c. property (nonatomic, copy) void(^selectedRole)(Role *role); I don't get this one (including the ^selectedRole part, kinda weird for me).3. The version you have here is slightly different from the one on github(specially on GameTabBarViewController) – Tree Nguyen Feb 02 '15 at 14:06
  • Sorry, but your comment touches many different aspects of iOS programming. I doubt that I can teach you all of this in comments. a) Delegates are used all over cocoa (the framework you use), you must be able to implement it to proceed. b) Roledatasource is a delegate/datasource to populate a tableview. I prefer to separate the code from view controllers to follow the SRP (Single Responsible Principle). Apple revealed that they do this too in [« Advanced User Interfaces with Collection Views»](https://developer.apple.com/videos/wwdc/2014/), though nearly all example codes violates SRP. – vikingosegundo Feb 02 '15 at 17:54
  • c) `property (nonatomic, copy) void(^selectedRole)(Role *role);` is a declaration of a block. these are constructs called closures. blocks are very important in apple's apis. – vikingosegundo Feb 02 '15 at 17:58
  • I cleaned up the tabbar controller a bit by using KVC — Key Value Coding. it works the same way. – vikingosegundo Feb 02 '15 at 18:00
-1

I think that I should put the array in the Tab bar Controller and connect it to the Role Table view (in order to maintain the behaviour like it is before) and connect it to my new Table view to do what I want to do.

The only problem I can think of is that since my program is small, adding this will not be a big problem. But if I have more vc, it's going to be so much pain.

Tree Nguyen
  • 1,198
  • 1
  • 14
  • 37