Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS Build a Blog Reader iPhone App Data Modeling Using a Custom Class

Stephen Hancocks
Stephen Hancocks
9,271 Points

Instances of custom classes

Hi, I've been working on my first iphone app and I'm getting some unexpected results. I think it should be pretty basic stuff so I think I'm doing something fundamentally wrong and believe it relates to instances of my classes.

I've been watching all of Amit Bijlani 's video's for the crystal ball app and the blog reader and using much of what I've learnt to progress my app.

I initially ran everything in a single page app with all code within a few methods of one viewcontroller and called everything from the viewdidload method. At this point I didn't have a storyboard setup, I just used NSLog to track progress and show results. At this point I got what I expected.

I then wanted to tidy up my code and follow the idea of MVC and build my storyboard.

I get all my data from HTML data of a website and form 5 NSMutableArrays. I then want to use 5 different viewControllers to display data from these arrays.

I can get and parse the HTML data ok and form my array's in my model 'DataHandling' class. If I 'NSLog' each array at the time of formation they show they contain exactly what I want. I run into problems when I load my second view and then want to refer to the arrays in the Datahandling class from the view controller.

in the viewcontroller.h file I have used @class and then used @property to make an instance of the datahandling class.

in my viewcontroller.m file I use datahandling *datahanling = [[datahandling alloc] init] to allow me to call the methods in the datahandling class. The issue is when I try to allocate a string from my array to the text value of a label it returns null and the array appears to be empty.

With my limited knowledge I think that when I allocate and initialise the class in my view controller I make another instance of the class that has not had any arrays formed within it. Is this correct? I really need to access the original instance of the class as the data takes a few seconds to download and parse. time that I don't want to repeat everytime I move to a new viewcontroller.

I'm currently sat at my work computer, not my home laptop, so I can't copy in any specific code, sorry.

Thanks Stephen Hancocks

Amit Bijlani
Amit Bijlani
Treehouse Guest Teacher

If you could share some code then we can try to help you out otherwise it's hard to tell what could be the problem.

Stephen Hancocks
Stephen Hancocks
9,271 Points

After posting again this morning I'm re-watching the blog reader app videos in an attempt to work out what's wrong. Wish me luck!

2 Answers

Stephen Hancocks
Stephen Hancocks
9,271 Points

Hi Amit Bijlani , thanks for responding. I was at work at the time so I didn't have the code to hand and on the review I agree, the question is a bit 'wordy'. Here's some code that will hopefully help some sort my issue.

//  LoginViewController.h

#import <UIKit/UIKit.h>
@class DataHandling;
@interface LoginViewController : UIViewController

@property (strong, nonatomic) DataHandling *dataHandling;
@property (weak, nonatomic) IBOutlet UITextField *distributorField;
@property (weak, nonatomic) IBOutlet UITextField *passwordField;

- (IBAction)loginPress:(id)sender;
- (void) performLoginSegue;
@end

I have a feeling I may be able to use the prepareforsegue method however, i am unsure. i don't recall you using it in the blog reader app.

//  LoginViewController.m
#import "LoginViewController.h"
#import "DataHandling.h"

@interface LoginViewController ()
@end

@implementation LoginViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
  self.dataHandling = [[DataHandling alloc]init];
}

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}

- (IBAction)loginPress1:(id)sender {
  NSString *distributor = [self.distributorField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  NSString *password = [self.passwordField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

  if ([distributor length] == 0 || [password length] == 0) {
    UIAlertView *loginAlertView = [[UIAlertView alloc] initWithTitle:@"Something is missing" message:@"Please make sure you have entered a distributor number and password" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
    [loginAlertView show];
    } 
  else {
    [self.dataHandling distributorLogin]; // learn how to make this a block so it completes before the segue is undertaken.
    [self performSegueWithIdentifier:@"loginSegue" sender:self];
  }
}
@end

Ideally i want to use a block to let all of the distributor login methods run before the view controller is changed. I have tried to call the performSegueWithIdentifier from the connectionDidFinishLoading but again it doesn't recognise the segue for the viewcontroller.

I haven't included the login / connection information as it all works as expected when i use NSLog to track progress and data collection. The following is how I form one of the six arrays by parsing the data. Again, they work as expected within datahandling.m.

//  DataHandling.h

#import <Foundation/Foundation.h>
#import "TFHpple.h"

@class LoginViewController;
@class ViewController;

@interface DataHandling : NSObject

@property (strong, nonatomic) LoginViewController *loginViewController;
@property (strong, nonatomic) ViewController *viewController;

@property (strong, nonatomic) NSMutableArray *globalSales;
// array properties
@property (nonatomic, copy) NSString *month;
@property (nonatomic, copy) NSString *sales;

@end



//  DataHandling.m

#import "DataHandling.h"
#import "LoginViewController.h"
#import "ViewController.h"

@implementation DataHandling

- (void) connectionDidFinishLoading:(NSURLConnection *)connection{
  // if data is successfully received, this method will be called by connection

  [self parsePrepared];

  [self globalSales];

  self.viewController = [[ViewController alloc]init];
  [self.viewController postLoginUpdate];
}

- (NSMutableArray *) globalSales{

  if(_globalSales == nil){

  NSLog(@"Third Parse");
  // Global Volume
  NSString *salesParseXpathQueryString = @"//table[@class='volume global']/tfoot/tr/td";
  NSArray *salesParseNodes = [_parsePrepared searchWithXPathQuery:salesParseXpathQueryString];


  // Build MutableArray of monthly sales
  _globalSales = [[NSMutableArray alloc] initWithCapacity:0];
  for (TFHppleElement *elementSales in salesParseNodes) {
    DataHandling *globalSalesInstance = [[DataHandling alloc]init];
    [_globalSales addObject:globalSalesInstance];

    for (TFHppleElement *child in elementSales.children){
      globalSalesInstance.sales = [[elementSales text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    }

    for (TFHppleElement *child in elementSales.children){
      globalSalesInstance.month = [[elementSales objectForKey:@"title"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // month
    }
  }

  // add error detection
  [_globalSales removeObjectAtIndex:0]; // first is always blank
}

so the connection is made, data downloaded and arrays formed. everything is working how i expect. now I want to use the data and display in my other view controllers.

//  ViewController.h

#import <UIKit/UIKit.h>

@class DataHandling;

@interface ViewController : UIViewController

@property (strong, nonatomic) DataHandling *dataHandling;
@property (weak, nonatomic) IBOutlet UILabel *caseCreditLabel;

- (void) postLoginUpdate; // called from dataHandling.m connectionDidFinishLoading

@end

//  ViewController.m

#import "ViewController.h"
#import "DataHandling.h"

@interface ViewController ()
@end

@implementation ViewController

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

- (void) postLoginUpdate
{
  self.salesLabel.text = [[[self.dataHandling globalSales] objectAtIndex:9] valueForKey:@"sales"];
}

@end

The salesLabel text doesn't update and I get (null) if i run NSLog on the statement. However, if I run the NSLog of this statement in datahandling.m directly after the array is formed it returns the NSString as expected.

I appreciate there is a lot of data above but any help is appreciated. I thought getting the data would prove to be the difficult bit but it's actually proving to be displaying it. As I said it's probably something fundament I'm doing wrong. Maybe I'm trying to run before I can work.

Thanks again. Steve

Amit Bijlani
STAFF
Amit Bijlani
Treehouse Guest Teacher

There are several things wrong with your code and it's kind of hard to decipher but I'll do my best to point out some of the glaring issues with DataHandling.

  1. Why are you creating an array of DataHandling objects? I'm referring to this line of code: DataHandling *globalSalesInstance = [[DataHandling alloc]init]; [_globalSales addObject:globalSalesInstance];

  2. Where are you initializing your URL connection?

  3. Why do you have a viewController property in your DataHandling? The job of the View Controller is to access and update the model and not the other way around. If you want to communicate back to a view controller then you should probably use a delegate. See my tutorial here on using delegates: https://teamtreehouse.com/forum/delegation-pattern

We have a new course coming out on Monday that explains a lot of these practices. On how you can handle authentication and downloading of data and refactor your code. The course is called "Build a Photo Browser App" which connects to the Instagram API via OAuth and displays photos using a collection view. I think you are trying to do things along the same lines.

Stephen Hancocks
Stephen Hancocks
9,271 Points

Thanks again Amit.

The more I think about my code and the more of the older videos I rewatch, the more I think I need to rethink my approach. My only previous 'coding' experience is in Visual Basic to do macro's for microsoft excel.

For your first point, I used this tutorial to help me access and parse the HTML data http://www.raywenderlich.com/14172/how-to-parse-html-on-ios The array's of data actually build perfectly.

For point 2, I didn't include the URL / connection side as it currently includes my login and password information which are used in a Post method to log into the site for my sales data [I haven't linked up the username and password fields]

As for point 3 I had a viewcontroller property as I tried to call a method in the view controller to update once the arrays had been formed.

I had noticed the new project coming out on Monday as it does cover a lot of things I am trying to do.

I'll keep going though. I'm determined to learn more and get my app finished.

I can only apologise for asking queries on poor code. I'm really eager to learn but I seem to have skipped the basics.

I look forward to your course on Monday.

Steve