-3

I am aware this question has been asked previously, but the answers provided have not solved my issue.

For instance, I have a very simple array of objects outlined in viewDidLoad:

@implementation MyViewController {
NSArray *tableData;
}

- (void)viewDidLoad
{
   [super viewDidLoad];
   tableData = [NSArray arrayWithObjects:@"Hello", @"My", @"Name", @"Is"];
   NSLog(@"My Data: %@", tableData);

which is called in a tableView using cellForRowAtIndexPath

cell.nameLabel.text = [tableData objectAtIndex:indexPath.row];

This works fine and the NSLog shows my array. However, when i outline tableData outside of viewDidLoad, my array is (null).

My question is, how do I make my array available for the tableView when it is specified outside of ViewDidLoad?

edit: Here is my specific code:

#import <UIKit/UIKit.h>
#import "PhotoView.h"
@interface FrontViewController : UIViewController

@property (nonatomic, retain) UITableView *tableView;

@end

#import "FrontViewController.h"
#import "StreamScreen.h"
#import "API.h"
#import "PhotoView.h"
#import "StreamPhotoScreen.h"
#import "PrivateViewController.h"
#import "SWRevealViewController.h"
#import "PhotoScreen.h"
#import "RearViewController.h"
#import "SimpleTableCell.h"



@interface FrontViewController()

// Private Methods:
- (IBAction)pushExample:(id)sender;

@end

@implementation FrontViewController{
    NSArray *tableData;

}

#pragma mark - View lifecycle


- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = NSLocalizedString(@"Front View", nil);

    SWRevealViewController *revealController = [self revealViewController];

    [self.navigationController.navigationBar addGestureRecognizer:revealController.panGestureRecognizer];

    UIBarButtonItem *revealButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"reveal-icon.png"]
                                                                     style:UIBarButtonItemStyleBordered target:revealController action:@selector(revealToggle:)];

    self.navigationItem.leftBarButtonItem = revealButtonItem;

    // This works if I uncomment
    //tableData = [NSArray arrayWithObjects:@"Hello", @"My", @"Name", @"Is", nil];

    [self refreshStream];

}




-(void)refreshStream {
    // call the "stream" command from the web API
    [[API sharedInstance] commandWithParams:
     [NSMutableDictionary dictionaryWithObjectsAndKeys:@"stream", @"command", nil]
                           onCompletion:^(NSDictionary *json) {
                               //got stream

                               [self showStream:[json objectForKey:@"result"]];
                               NSMutableArray *myData = [[NSMutableArray alloc] init];
                               myData = [json objectForKey:@"result"];
                               NSArray *userNameData = [myData valueForKey:@"username"];


                               [self loadData];

                               tableData = userNameData;


                               [self.tableView reloadData];
                               // I can see my json array in NSLog
                               NSLog(@"here's the results: %@", tableData);
                           }];

}
//This doesn't work either
//-(void)loadData {

    // Add the data to your array.
    //tableData = [NSArray arrayWithObjects:@"Hello", @"My", @"Name", @"Is", nil];
    //NSLog(@"My Data: %@", tableData);

   // Now load the table view.
  //  [self.tableView reloadData];
//}


-(void)showStream:(NSArray*)stream {

    for (int i=0;i<[stream count];i++) {
        NSDictionary* photo = [stream objectAtIndex:i];


    }
    NSArray *checkData = [stream valueForKey:@"username"];
    //I can see my data in NSLog
    NSLog(@"here's the results: %@", checkData);
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [tableData count];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 78;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *simpleTableIdentifier = @"SimpleTableCell";

    SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
    if (cell == nil)
    {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"SimpleTableCell" owner:self options:nil];
        cell = [nib objectAtIndex:0];
}

    cell.nameLabel.text = [tableData objectAtIndex:indexPath.row];
    cell.thumbnailImageView.image = [UIImage imageNamed:[thumbnails objectAtIndex:indexPath.row]];
    cell.prepTimeLabel.text = [prepTime objectAtIndex:indexPath.row];

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"didSelectRowAtIndexPath");
    /*UIAlertView *messageAlert = [[UIAlertView alloc]
 initWithTitle:@"Row Selected" message:@"You've selected a row" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];*/
UIAlertView *messageAlert = [[UIAlertView alloc]
                             initWithTitle:@"Row Selected" message:[tableData objectAtIndex:indexPath.row] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];

    // Display the Hello World Message
    [messageAlert show];

    // Checked the selected row
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    cell.accessoryType = UITableViewCellAccessoryCheckmark;

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"willSelectRowAtIndexPath");
    if (indexPath.row == 0) {
        return nil;
     }

   return indexPath;
}
@end

enter image description here

AB567
  • 141
  • 8
  • So you are basically asking how to assign variables/etc... to your array outside of viewDidLoad? If thats the case, just make your own method and use that. –  May 01 '15 at 07:35
  • " when it is specified outside of ViewDidLoad", do you want access the data globally throughout the app. – Vizllx May 01 '15 at 07:37
  • This data will be available outside also, if your tableview doesn't show this value with required cell, you may forgot to `reloadData` of `UITableview` after initialising in `viewDidLoad` method. – Viral Savaj May 01 '15 at 07:37
  • please show the code where the array is `(null)` – luk2302 May 01 '15 at 07:40
  • I have edited my question to show my exact code. thanks. – AB567 May 01 '15 at 08:08
  • Try initialising `myData`, `userNameData` and `tableData` . – Bista May 01 '15 at 08:19

3 Answers3

2
-(void)refreshStream {
    // call the "stream" command from the web API
    [[API sharedInstance] commandWithParams:
        [NSMutableDictionary dictionaryWithObjectsAndKeys:@"stream", @"command", nil] 
        onCompletion:^(NSDictionary *json) {
            //got stream

            [self showStream:[json objectForKey:@"result"]];
            NSMutableArray *myData = [json objectForKey:@"result"];
            NSArray *userNameData = [myData valueForKey:@"username"];
    }];

    tableData = userNameData;

    [self.tableView reloadData]; 
}

You're falling into a very common trap with asynchronous programming here.

commandWithParams takes a completion block, which is where you are getting the data out of the JSON. This block is not executed until the API call has returned. The sequence of events that happens when you run this code is:

  1. commandWithParams is called
  2. tableData is assigned to the contents of userNameData (which presumably you've also declared somewhere else otherwise this would not even compile)
  3. reloadData is called
  4. .... time passes
  5. The completion block is executed and the JSON is read out into local variables, which are then instantly destroyed.

You need to move the two lines (points 2 and 3 in the list above) inside the completion block. There will be no data for your table until the block returns.

jrturton
  • 118,105
  • 32
  • 252
  • 268
  • Holy moly! I didn't think of that. So the issue is that @Amy is loading data asynchronously and so when she calls the tableview to reload, the data isn't actually ready yet. So just one thing then, when one loads data asynchronously, they must make sure that the methods in charge of putting that data in say a tableview, must be called at the end of the asynchronous method block and not in some other location?? –  May 01 '15 at 09:05
  • I have tried putting 2 & 3 in the block and yet I still get a blank screen. Maybe i'm missing something with point 2. Could you provide some example code? when i try making point 2 simply tableData = [NSArray arrayWithObjects:@"Hello", @"My", @"Name", @"Is"]; it still returns a blank screen. I only get any output from the table when i use tableData = [NSArray arrayWithObjects:@"Hello", @"My", @"Name", @"Is"]; in ViewDidLoad. thank you – AB567 May 01 '15 at 09:06
  • @Amy It is because jrturton forgot to tell you that as well as using his solution, you **also** need to initialize the NSMutableArray, otherwise it wont store any data. Do it like so: ```NSMutableArray *myData = [[NSMutableArray alloc] init]; myData = [json objectForKey:@"result"];``` –  May 01 '15 at 09:07
  • Are you definitely getting something back from that JSON array? If that array is empty, that would be the expected result. If you put your hello - my - name - is code inside the completion block, does it work there? Put breakpoints inside the completion block and check the values you are seeing. – jrturton May 01 '15 at 09:08
  • @Dan, no, that's not necessary. You'd only need to do that if you were doing `[myData addObject:]`. – jrturton May 01 '15 at 09:08
  • @jrturton Really? Oh.... But if you don't do it, it wont store data. I'm talking about NSMutableArray not the normal NSArrays??? –  May 01 '15 at 09:09
  • @Dan `myData = [json objectForKey:@"result"];` replaces anything you did before to myData with the contents of the JSON object. – jrturton May 01 '15 at 09:10
  • @jrturton Oh right..... I see. Thanks for that, I thought you always had to do it. –  May 01 '15 at 09:11
  • @jrturton If i put the hello my name is code inside the block, it returns an empty table – AB567 May 01 '15 at 09:15
  • Yes breakpoints get hit and i can see my json array. If i put 2 & 3 into the block with an NSlog I can also view my items that way. But nothing on the table. It's like I just need a way to get my data from inside that block and enable it to be used outside. thank you. – AB567 May 01 '15 at 09:25
  • can you update your question with the new, exact, code, because it really ought to be working... can you see what thread is running when the breakpoints are hit? (this will be in the call stack on the left hand side) – jrturton May 01 '15 at 09:31
  • Almost definitely self.tableView is nil. It's not an outlet and you aren't assigning to it anywhere. – jrturton May 01 '15 at 12:19
0

Ok my understanding of your question is that you want to assign variables to your NSArray in another method (not viewDidLoad) and then load the table view.

This is simple, just make a method which is in charge of adding the data to your array and then reload your table view like so:

-(void)viewDidLoad {
    [super viewDidLoad];

    // Call your method.
    [self loadData];
}

-(void)loadData {

    // Add the data to your array.
    tableData = [NSArray arrayWithObjects:@"Hello", @"My", @"Name", @"Is"];
    NSLog(@"My Data: %@", tableData);

    // Now load the table view.
    [myTableView reloadData];
}

Update 1

It would be much more helpful if you could share your code with us. How your and setting up your tableview, when its being called/etc....

Update 2

Ok well it seems obvious what yoru issue is. Your table view will never load like that. You need to call the tableview reloadData method outside the cellForRowAtIndexPath method.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    static NSString *simpleTableIdentifier = @"SimpleTableCell";

    SimpleTableCell *cell = (SimpleTableCell *)[self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];

    if (cell == nil) {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"SimpleTableCell" owner:self options:nil];
        cell = [nib objectAtIndex:0];
    }

    cell.nameLabel.text = [tableData objectAtIndex:indexPath.row];
    cell.thumbnailImageView.image = [UIImage imageNamed:[thumbnails objectAtIndex:indexPath.row]];
    cell.prepTimeLabel.text = [prepTime objectAtIndex:indexPath.row];

    return cell;   
}

So the method call [self.tableView reloadData]; should only be called in your refreshStream method.

Update 3

You need to initialize your NSMutableArray before you can add data to it. initialize it in your refreshStrem method like so:

-(void)refreshStream {

    // call the "stream" command from the web API
    [[API sharedInstance] commandWithParams:[NSMutableDictionary dictionaryWithObjectsAndKeys:@"stream", @"command", nil] onCompletion:^(NSDictionary *json) {
    //got stream

        [self showStream:[json objectForKey:@"result"]];

        NSMutableArray *myData = [[NSMutableArray alloc] init];
        myData = [json objectForKey:@"result"];

        NSArray *userNameData = [myData valueForKey:@"username"];
    }];

    tableData = userNameData;
    [self.tableView reloadData];
}

Update 4

Ok well after reading @jrturton answer, I think its safe to assume that my answer is rubbish. To anyone reading my answer, please view @jrturton post.

  • I have edited my question with my exact code. Thanks. – AB567 May 01 '15 at 08:07
  • @Amy Good but not quite useful yet. Can you update your question to show us where you are adding the elements from the array to your tableview. I suspect there is something wrong with the way you are inserting the data to your tableview. –  May 01 '15 at 08:09
  • @Amy I was kind of hoping your would share your ```cellForRowAtIndexPath``` method with us. That is the method where you actually add the data from your array to your table view. –  May 01 '15 at 08:16
  • My table does load when the data is in viewDidLoad though so that doesn't seem to be causing the issue. – AB567 May 01 '15 at 08:26
  • @Amy You should **not** be calling ```[self.tableView reloadData];``` in the ```cellForRowAtIndexPath``` method. –  May 01 '15 at 08:28
  • I have deleted [self.tableView reloadData]; but it doesn't solve the issue. I can still only view my data when i put the data in viewDidLoad and not when it is in the refreshStream method. Thanks anyway. – AB567 May 01 '15 at 08:30
  • @Amy Ok I think I got it, you are not initializing your NSMutableArray. Have a look at update 3 in my answer. –  May 01 '15 at 08:33
0

Well I feel pretty sheepish. The answer was simple. I was so hung up on Json and API that all I didn't check the basics. All I need was in my .h file:

@property (strong, nonatomic) IBOutlet UITableView *tableView;

I had originally had:

@property (nonatomic, retain) UITableView *tableView;
AB567
  • 141
  • 8
  • That was not all you needed :) The other changes were important as well! Glad you have it working now though! – jrturton May 01 '15 at 13:13