How to prevent textfields from getting empty when adding/removing UITableView rows? - objective-c

I'm creating a UITableView in which product information can be added. In each row, the user can add information about a product, and, obviously, the user can set the number of rows himself.
the user can add or remove one row a time by tapping either the "add row" or "remove row" button in the NavigationBar. this is how it works:
- (void)viewDidLoad
{
[super viewDidLoad];
tableRows = [NSNumber numberWithInt:12];
}
-(void) addRow
{
NSNumber *addRow =[NSNumber numberWithInt:1];
tableRows= [NSNumber numberWithInt:(tableRows.intValue + addRow.intValue)];
[self.tableView reloadData];
NSLog(#"%#", tableRows);
}
-(void) removeRow
{
NSNumber *addRow =[NSNumber numberWithInt:1];
tableRows= [NSNumber numberWithInt:(tableRows.intValue - addRow.intValue)];
[self.tableView reloadData];
NSLog(#"%#", tableRows);
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return tableRows.intValue;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CustomCellIdentifier = #"CustomCellIdentifier ";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell"
owner:self options:nil];
for (id oneObject in nib) if ([oneObject isKindOfClass:[CustomCell class]])
cell = (CustomCell *)oneObject;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSUInteger *row = [indexPath row];
return cell;
}
The editing works perfect but when I add or remove a row, the text I inserted in the textfields of my tableview disappears.
does anybody know how to prevent this?

A couple things: The table view doesn't have responsibility to remember what's in each of the cells. It throws away cells as the scroll away and asks the datasource to initialize them again via cellForRowAtIndexPath. Reloaddata - which you use in your add/remove methods - will cause the table to refresh all of the visible cells. Don't expect anything to appear in your table that isn't setup in cellForRowAtIndexPath.
Next, your "model" for this table is an NSNumber "tableRows" indicating the number of rows. This is an insufficient model for a table view. Replace it with an NSMutableArray. At the very least, this array should contain strings representing the state of each text field. (and it might need even more elaborate objects, but start with strings).
With that, your view controller class will look more like this...
// this is your table's model
#property (nonatomic, strong) NSMutableArray *rows;
// in init for the class
_rows = [NSMutableArray array];
// somewhere else, put some data in it
[self.rows addObject:#"Foo"];
[self.rows addObject:#"Bar"];
Now your datasource methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.rows.count;
}
Then, in cellForRowAtIndexPath:
NSUInteger *row = [indexPath row]; // as you have it
NSString *rowText = self.rows[row]; // this is new syntax, the old way is [self.rows objectAtIndex:row];
// your CustomCell needs to provide a way to get at the textField it contains
// it might do this with an outlet or viewWithTag...
cell.myTextField.text = rowText;
return cell;
Finally, text fields in the cells pose a particular challenge. How to save their current state when the view isn't scrolling. This problem has been asked and answered multiply in SO (here, for example). In a nutshell, the most common solution is to make the view controller the delegate of the text fields in the cells. Then, on textFieldDidEndEditing, save the value of the textField in your model like this...
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSIndexPath *indexPath = [self indexPathOfCellWithSubview:textField];
self.rows[indexPath.row] = textField.text;
}
// I think this is the best way to get the index path of a cell, given some subview it contains
- (NSIndexPath *)indexPathOfCellWithSubview:(UIView *)view {
while (view && ![view isKindOfClass:[UITableViewCell self]]) {
view = view.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)view];
}
EDIT Say there's more to the model than just a single string. This is where you would apply a custom subclass of NSObject.
// MyModel.h
#interface MyModel : NSObject
#property (strong, nonatomic) NSString *itemName;
#property (assign, nonatomic) CGFloat price;
#property (strong, nonatomic) NSString *imageFileName;
#property (strong, nonatomic) UIImage *image;
- (id)initWithItemName:(NSString *)itemName price:(CGFloat)price imageFileName:(NSString *)imageFileName;
- (NSString *)stringPrice;
- (void)setStringPrice:(NSString *)stringPrice;
#end
// MyModel.m
#implementation MyModel
- (id)initWithItemName:(NSString *)itemName price:(CGFloat)price imageFileName:(NSString *)imageFileName {
self = [self init];
if (self) {
_itemName = itemName;
_price = price;
_imageFileName = imageFileName;
}
return self;
}
// override the image getter to "lazily" create and cache the image
// if the images are on the web, this will require a slighly more elaborate method
// employing NSURLConnection.
- (UIImage *)image {
if (!_image) {
_image = [UIImage imageNamed:self.imageFileName];
}
return _image;
}
// added these to show you how you can conveniently encapsulate other
// behavior, like type conversion or validation, though, real ones of these
// would probably use NSNumberFormatter
- (NSString *)stringPrice {
return [NSString stringWithFormat: #"%.2f", self.price];
}
- (void)setStringPrice:(NSString *)stringPrice {
self.price = [stringPrice floatValue];
}
Now you can create one like this and add it to your table. (Be sure to #import "MyModel.h")
[self.rows addObject:[[MyModel alloc] initWithItemName:#"Toaster" price:39.95 imageFileName:#"toaster.png"]];
The view controller containing the table stays more or less the same (when you change one class a lot and change a closely related class very little, it tells you that your OO design is probably pretty good). For the fancy model replacing the string, we need to change cellForRowAtIndexPath...
NSUInteger *row = [indexPath row];
MyModel *myModel = self.rows[row];
cell.itemNameTextField.text = myModel.itemName;
cell.priceTextField.text = [myModel stringPrice];
cell.imageView.image = myModel.image;
// additional OO idea: teach your cell how to configure itself and move the foregoing code there
// [cell configureWithModel:myModel];
return cell;
ANOTHER EDIT: We can teach this model how to post itself to a remote web service as follows:
- (void)post {
NSString *hostStr = #"http://myhost/create_product.php";
NSURL *url = [NSURL URLWithString:hostStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSString *post =[NSString stringWithFormat:#"item_name=%#&price=%#",self.itemName, [self stringPrice];
NSString *postEscaped = [post stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *postData = [postEscaped dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
[request setHTTPBody:postData];
[request setValue:#"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"response %#", string);
} else {
NSLog(#"error %#", error);
}
}];
}
Declare this method in the .h, add other fields to the post as you see fit (e.g. the image file name, etc.)
In your view controller, pick out the action that means the user wants to commit the new row (maybe it's when the text field is finished editing?), and add this...
// text field finished editing
MyModel *myModel = self.rows[indexPath.row];
myModel.itemName = textField.text;
[myModel post];
Since the image will probably come from your remote service, you'll want to change the lazy loading image getter I added earlier. The right way to load this image is asynchronously, but doing so complicates the interaction with the table view too much to discuss here. Refer to apple docs or this SO post to learn more about that. In the meantime, here's the quick -- but basically wrong -- way to get the image synchronously...
- (UIImage *)image {
if (!_image) {
// note - now the file name must be of the form #"http://host/path/filename.png"
NSURL *url = [NSURL URLWithString:self.imageFileName
_image = [UIImage imageWithContentsOfURL:url];
}
return _image;
}

It would be helpful to see your code for cellForRowAtIndexPath, we need to know more about the model you intend to store data in.
When you delete a row from the table, that cell is thrown out, and the tableview will not remember the contents automatically. You must save the changes in a model object as they occur, and then use that to populate the cell's contents when returning a cell from cellForRowAtIndexPath.

Related

Empty UITableViewController with async calls

I am working on using NSURLSession and JSON serialization to fetch content from my site. The async calls and getting the JSON data work perfectly. My issue is, when it comes to displaying the data in the TableviewController, I put an NSLog statement to see if there is data and there is, but that cell.textlable.text never updates. I'm guessing the issue is the threads but I can't figure it out. Can you help?
#interface MainTableViewController :
UITableViewController<LokalModelProtocol>
#property (strong,nonatomic) NSMutableArray* arr;
#end
#implementation MainTableViewController
#synthesize arr;
- (void)viewDidLoad {
[super viewDidLoad];
arr = [[NSMutableArray alloc]init];
LokalModel *lokal = [[LokalModel alloc]init];
lokal.delegate=self;
[lokal downloadItems];
}
-(void)itemsDownloaded:(NSMutableArray *)items
{
arr=items;
//NSLog(#"%#", items);
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Incomplete implementation, return the number of sections
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
#warning Incomplete implementation, return the number of rows
// return 1;
return [arr count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"mainCell" forIndexPath:indexPath];
PostModel *post = [[PostModel alloc]init];
post =[arr objectAtIndex:indexPath.row];
NSLog(#"%#", post.postTitle); ////this outputs the correct strings///////
cell.textLabel.text =[NSString stringWithFormat:#"%#", post.postTitle];
cell.detailTextLabel.text = post.postTitle;///neither of these do//////
return cell;
}
#end
#protocol LokalModelProtocol <NSObject,NSURLSessionDelegate>
+(void)itemsDownloaded:(NSMutableArray*)items;
#end
#interface LokalModel : NSObject
-(void)downloadItems;
#property (strong, nonatomic) NSMutableData* thedata;
#property (strong, nonatomic) NSString* urlString;
#property (strong, nonatomic) NSURL* theUrl;
#property (strong,nonatomic) id<LokalModelProtocol>delegate;
+(void)parseJson:(NSData*)data;
#end
id<LokalModelProtocol>delegate;
#implementation LokalModel;
#synthesize thedata,urlString,theUrl,delegate;
-(void)downloadItems{
NSURL *theUrl = nil;
static NSString* urlString = #"https://balalatet.com/wp-json/wp/v2/posts";
theUrl=[NSURL URLWithString:urlString];
NSURLSession *currentSession= [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [currentSession dataTaskWithURL:theUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error){
[NSException raise:#"error" format:#"%#",error.localizedDescription];
NSLog(#"error1");
}
else{
NSLog(#"success");
[LokalModel parseJson:data];
}
}];
[task resume];
}
+(void)parseJson:(NSData*)data{
NSArray *jsonResults = [[NSArray alloc]init];
NSError *jsonerror;
jsonResults =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonerror];
if (jsonerror)
[NSException raise:#"json error" format:#"%#",jsonerror.localizedDescription];
NSMutableArray *posts = [[NSMutableArray alloc] init];
NSMutableDictionary *jsonElenent =[NSMutableDictionary dictionary];
for (NSMutableDictionary *d in jsonResults)
{
jsonElenent=d;
PostModel *thePost=[[PostModel alloc]init];
thePost.postId= jsonElenent[#"id"];
thePost.postDate= jsonElenent[#"date"];
thePost.postDategmt= jsonElenent[#"date_gmt"];
thePost.postGuid= jsonElenent[#"guid"];
thePost.postSlug= jsonElenent[#"slug"];
thePost.postStatus= jsonElenent[#"status"];
thePost.postSticky= jsonElenent[#"sticky"];
thePost.postPingStatus= jsonElenent[#"ping_status"];
thePost.postType= jsonElenent[#"type"];
thePost.postCommentStatus= jsonElenent[#"comment_status"];
thePost.postTags= jsonElenent[#"tags"];
thePost.postTitle= jsonElenent[#"title"];
thePost.postTemplate= jsonElenent[#"template"];
thePost.postLink= jsonElenent[#"link"];
thePost.postMeta= jsonElenent[#"meta"];
thePost.postModified= jsonElenent[#"modified"];
thePost.postModifiedgmt= jsonElenent[#"modified_gmt"];
thePost.postFeaturedMedia= jsonElenent[#"featured_media"];
thePost.postFormat= jsonElenent[#"format"];
thePost.postLinks= jsonElenent[#"links"];
thePost.postAuthor= jsonElenent[#"author"];
thePost.postContent= jsonElenent[#"content"];
thePost.postCategory= jsonElenent[#"category"];
thePost.postExcerpt= jsonElenent[#"excerpt"];
NSLog(#"%#", thePost.postTitle);
[posts addObject:thePost];
}
dispatch_async(dispatch_get_main_queue(), ^{
[delegate itemsDownloaded:posts];
});
}
#end
Update
my apologies as part of my debugging info is incorrect. the nslog output did not come from the cellForRowAtIndexPath method. in fact the arr array remains empty because the
(void)itemsDownloaded:(NSMutableArray *)items
is never called. im sure i setup the protocol correctly. any thoughts on why the MainTableViewControllerClass cant get the data?
update
so i realized that i forgot to remove the line
id<LokalModelProtocol>delegate;
that i put right before the #implementation in LokalModel. but now doing so causes an error "unrecognized selector sent to instance" at the line
[delegate itemsDownloaded:posts];
I aslo tried
[self.delegate itemsDownloaded:posts];
but it throws the same exception.
Solved
My protocol method had to be an instance method, and i had it set as a class method.
Before return your cell add try to add this code in cellForRowIndexPath
[cell layoutIfneeded];
I believe you have to add a registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: prior to using dequeueReusableCellWithIdentifier:forIndexPath: (in viewDidLoad for example)
From the documentation: https://developer.apple.com/documentation/uikit/uitableview/1614878-dequeuereusablecellwithidentifie?language=objc
Important
You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method.

Load data into tableview form cells

I have a program where a UITableView contains custom cells loaded from nibs. These cells have textfields and a UIImage. I've been passing the information they contain to a custom class and encoding/decoding the class for data persistence. When I want to load the data, I put the information from the class into the cell. This works fine for 1 cell, but not for more than one. I've checked, and the classes are being written to file correctly.
This is my retrieval method:
//Fills an array if the file exists, otherwise returns nil
- (NSMutableArray*) findFile: (NSString *) add
{
if ([[NSFileManager defaultManager] fileExistsAtPath:[self saveFilePath:add]])
{
NSString *temp = [add stringByAppendingString:#"dat"];
namesIndexer = [[NSMutableArray alloc] initWithContentsOfFile:[self saveFilePath:temp]];
if (namesIndexer == nil) return nil;
NSMutableArray *thing = [NSMutableArray new];
for (NSString *place in namesIndexer)
{
temp = [add stringByAppendingString:place];
PTextHolder *p = [NSKeyedUnarchiver unarchiveObjectWithFile:[self saveFilePath:temp]];
[thing addObject:p];
}
return thing;
}
else
{
return nil;
}
}
Note that this is in a different class, and it calls the method from the holder.
//Returns a cell to be used at a row, populates it from the holder object
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *personCellId = #"personID";
UINib *nib = [UINib nibWithNibName:#"PersonCell" bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:personCellId];
PersonCell *cell = [tableView dequeueReusableCellWithIdentifier:personCellId];
cell.owner = tableView;
if (mineTable == nil) mineTable = tableView;
cell.delegated = formDataStorage;
[formDataStorage putWhatShouldBeInThisCellForThisRowInIt:cell:(int*)indexPath.row];
cell.currentRow = [[NSNumber alloc] initWithInt:indexPath.row];
return cell;
}
Here's the method it calls:
- (void) putWhatShouldBeInThisCellForThisRowInIt: (PersonCell *) someCell: (int *) someRow
{
if ((NSUInteger) someRow >= cake.count)
{
NSLog(#"The cake has been undercooked");
return;
}
PTextHolder *temp = [cake objectAtIndex:(NSUInteger) someRow];
someCell.firstName.text = temp.first;
someCell.lastName.text = temp.last;
someCell.middleName.text = temp.middle;
someCell.suffixName.text = temp.suffix;
someCell.email.text = temp.email;
someCell.theSignature.image = temp.sig;
}
Anything look wrong here/would cause only one cell to be loaded?
I would check first the number of items in the array with
[array count]
, if the number of items is equal to 1, then the problem is as you guessed with the encoding/decoding.
If not, your code is right and the problem is with your code to load the cells.
By the way, why dont you store your array of "cellInfoClass" directly using:
[NSKeyedArchiver archiveRootObject:array toFile:filePath]
and retrieve directly the array.
I guess you already added the encoding/coding code to your class, if not is like that:
/**
* Returns an object initialized from data in a given unarchiver. (required)
*
* #param decoder: An unarchiver object.
*/
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
// If parent class also adopts NSCoding, replace [super init]
// with [super initWithCoder:decoder] to properly initialize.
[self setName:[coder decodeObjectForKey:#"name"]];
[self setId:[coder decodeIntForKey:#"id"]];
[self setDomain:[coder decodeObjectForKey:#"domain"]];
}
return self;
}
/**
* Encodes the receiver using a given archiver. (required)
* #param coder: An archiver object.
*/
- (void)encodeWithCoder:(NSCoder *)coder{
// If parent class also adopts NSCoding, include a call to
// [super encodeWithCoder:encoder] as the first statement.
[coder encodeObject:name forKey:#"name"];
[coder encodeInt:id forKey:#"id"];
[coder encodeObject:domain forKey:#"domain"];
}

Populate UITableView in ViewController class from separate DataController class that is using grand central dispatch

I have a UITableView in a ViewController class. The ViewController class uses a custom dataController (specified in the AppDelegate). In the dataController class I'm fetching some JSON from the web, parsing it to an NSMutableArray, then using that data to populate the UITableView in the ViewController.
This all works great, except there is a noticeable lag when the app starts up since it takes time to get the JSON and work with it. I'd like to show an empty UITableView with an activity indicator while this data is loading. Unfortunately whenever I put the code in the dataController class into a dispatch queue, the UITableView is never populated with data (the data is loaded according to the log). All I see is a blank table.
I guess my main issue is I don't know how to set up a queue in the dataController class and then update the UI with the data in that queue but in another class.
Relevant code:
from dataController class:
- (void)initializeDefaultDataList {
NSMutableArray *dataList = [[NSMutableArray alloc] init];
self.masterDataList = dataList;
dispatch_queue_t myQueue = dispatch_queue_create("name.queue.my", NULL);
dispatch_async(myQueue, ^{
NSString *jsonString = [JSONHelper JSONpostString:#"http://webservice/getData"];
NSError *jsonError = nil;
//convert string to dictionary using NSJSONSerialization
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData: [jsonString dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &jsonError];
if (jsonError) NSLog(#"[%# %#] JSON error: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), jsonError.localizedDescription);
NSArray *dataArray = [jsonResults objectForKey:#"d"];
for (NSString *dataItem in dataArray) {
[self addDataWithItem:dataItem];
}
});
}
from AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MyMasterViewController *firstViewController = (MyMasterViewController *)[[navigationController viewControllers] objectAtIndex:0];
MyDataController *aDataController = [[MyDataController alloc] init];
firstViewController.dataController = aDataController;
return YES;
}
from ViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//would this go here?
dispatch_async(dispatch_get_main_queue(), ^{
MyObject *objectAtIndex = [self.dataController objectInListAtIndex:indexPath.row];
[[cell textLabel] setText:objectAtIndex.name];
});
return cell;
}
In case you couldn't tell I'm really new to iOS and Objective C. Any help or hints you can give would be greatly appreciated. I'm not even sure if I'm expressing my question properly - it just seems that what I want to do shouldn't be this difficult. Thanks!
EDIT
Ok, so maybe this is a life cycle issue. Just realized that anything I set within the async block is nil outside the block, at least it is until it's too late to make a difference. That's why cellForRowAtIndexPath is never called - because the masterDataList being passed to the UITableView is empty. Tested this by initializing
__block NSString *s = [[NSString alloc] init];
outside the block, then setting a value inside the block:
s = #"Testing...";
and finally NSLogging the value of s after the block has supposedly run. But obviously the block hadn't run yet because s was nil.
It looks like you're doing the right thing to get back on the main thread after your work is done, but you haven't told the table view it needs to show the new data. [self.tableView reloadData] ought to help.
As I discovered in posts such as this one, data set within the async dispatch cannot be used outside the queue. As I understand it, the whole idea of GCD is that it determines when it's best to run and dispose of data.
As a result, I ended up splitting up my code so I was only using the DataController class to, well, control data (I know, revolutionary) and moved all the GCD parts to my ViewController. Amended code:
DataController class:
- (void)initializeDefaultDataList {
NSMutableArray *dataList = [[NSMutableArray alloc] init];
self.masterDataList = dataList;
}
ViewController class:
#interface ObjectMasterViewController () {
__block NSString *jsonString;
}
#end
...
- (void)getJSONString
{
jsonString = [JSONHelper JSONpostString:#"http://webservice/getData"];
}
...
- (void)initData {
NSError *jsonError = nil;
//convert string to dictionary using NSJSONSerialization
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData: [jsonString dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &jsonError];
if (jsonError) NSLog(#"[%# %#] JSON error: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), jsonError.localizedDescription);
NSArray *dataArray = [jsonResults objectForKey:#"d"];
//loop through array and add items to list
for (NSString *dataItem in dataArray) {
[self addDataWithItem:dataItem];
}
}
...
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_queue_t myQueue = dispatch_queue_create("name.queue.my", NULL);
dispatch_async(myQueue, ^{
//initalize service url string
[self getJSONString];
dispatch_async(dispatch_get_main_queue(), ^{
//retrieve data
[self initData];
//reload tableView with new data
[self.tableView reloadData];
});
});
}
Hope this can help someone who might be in the same boat I was in.

How to Fix EXC_BAD_ACCESS on NSArray Property?

This is yet another EXC_BAD_ACCESS question. Although I've done my homework and am certain that I am not over-releasing my NSArray.
So here is a snippet of my code:
tableData = [NSDictionary dictionaryWithJSONString:JSONstring error:&error];
//Collect Information from JSON String into Dictionary. Value returns a mutli
dimensional NSDictionary. Eg: { value => { value => "null"}, etc }
NSMutableArray *t_info = [[NSMutableArray alloc] init];
for(id theKey in tableData)
{
NSDictionary *get = [tableData objectForKey:theKey];
[t_info addObject:get];
[get release];
} // converting into an NSArray for use in a UITableView
NSLog(#"%#", t_info);
//This returns an Array with the NSDictionary's as an Object in each row. Returns fine
if (tvc == nil)
{
tvc = [[tableViewController alloc] init]; //Create Table Controller
tableView.delegate = tvc;
tableView.dataSource = tvc;
tvc.tableView = self.tableView;
tvc.tableData = t_info; //pass our data to the tvc class
[tvc.tableView reloadData];
}
...
Now in my TableViewController Class:
#implementation tableViewController
#synthesize tableData, tableView;
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count]; //Returns X Amount Fine.
}
- (UITableViewCell *)tableView:(UITableView *)the_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *MyIdentifier = [NSString stringWithFormat:#"MyIdentifier"];
UITableViewCell *cell = [the_tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
NSLog(#"%#", tableData); //** CRASHES!!**
cell.textLabel.text = #"This is a test";
return cell;
}
If I were to comment out that NSLog, it'll work fine and return "this is a test" on each table row.
This one has really got me stumped, all the articles I have around about this problem is generally related to retain/memory issues.
Also, another important point.
If I were to pass through my original (NSDictionary) tableData from my first class code and run the same script in my tableViewController - I can NSLog the object perfectly fine.
The only time you need to release an object is if you have explicitly allocated it by way of new, alloc, or copy.
NSMutableArray *t_info = [[NSMutableArray alloc] init];
for(id theKey in tableData)
{
NSDictionary *get = [tableData objectForKey:theKey];
[t_info addObject:get];
[get release];
}
You shouldn't be releasing get here. By doing this, you're releasing the reference that the tableData dictionary is holding onto, which is bad. My guess is that this is what is causing the problem that you're encountering.
If I'm not mistaken, the reason why [tableData count] returns the expected value is because the array is still holding onto the references that have been released.

UITAbleView Giving Error

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//NSLog(#"Array: %#",rows);
return [rows count];// AT THIS LINE
}
Program received signal: “EXC_BAD_ACCESS”
THANKS FOR THE REPLY
Actually I have attached it to the WebPage By NSUrl where I have made a PHP array and I have created a NSLOG where I am getting the Values in the array form but When It exceute the line return [rows count];. It gives error when I am writting statically return 2; then it execute. I am explaining to you what I am doing. I am initialising the NIb with
Name tableViewController=[[JsonTestViewController alloc] initWithNibName:#"JsonTestViewController" bundle:nil];
In JsonTestViewController.m
I have this code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//NSLog(#"Array: %#",rows);
return [rows count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Configure the cell.
NSDictionary *dict = [rows objectAtIndex: indexPath.row];
NSString *strlb1=[dict objectForKey:#"block"];
NSString *strlb2=[dict objectForKey:#"name"];
strlb1=[strlb1 stringByAppendingString:#" , "];
strlb1=[strlb1 stringByAppendingString:strlb2];
NSString *str1=#"FPS : ";
NSString *str2=[dict objectForKey:#"p_hours"];
NSString *strpinf;
if([str2 isEqualToString:#"FP"])
{
strpinf=#"Free Parking";
}
else if([str2 isEqualToString:#"12"])
{
strpinf=#"2 hours";
}
else if([str2 isEqualToString:#"14"])
{
strpinf=#"4 hours";
}
else if([str2 isEqualToString:#"MP"])
{
strpinf=#"Metered Parking";
}
str1=[str1 stringByAppendingString:strpinf];
cell.textLabel.text =strlb1;
cell.detailTextLabel.text = str1;
return cell;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:#"SITE URL"];
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:url];
NSData *jsonData = [jsonreturn dataUsingEncoding:NSUTF32BigEndianStringEncoding];
NSError *error = nil;
NSDictionary * dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:&error];
if (dict)
{
rows = [dict objectForKey:#"users"];
}
NSLog(#"Array: %#",rows);
[jsonreturn release];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
can you give more info? This can be anything, but most likely, rows is pointing to memory where a valid array used to be. How did you create the rows array?
For example, your rows array or dictionary not longer pointing to valid memory if you created the rows array as an autoreleased object through a factory method in another method.
Here's another question that's pretty close to what you're describing:
EXC_BAD_ACCESS signal received
EDIT:
So looking at the code you provided, with these lines there are some possibilities:
NSDictionary * dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:&error];
if (dict) { rows = [dict objectForKey:#"users"]; }
the deserializeAsDictionary method can return either an autoreleased dictionary or NULL. so one possibility is that rows = NULL. when you try [rows count], your program will crash. Check and see what's in error, might give you some clues.
This will cause an error even when you explicitly return 2 for numberOfRowsInSection: because in cellForRowAtIndexPath:, you're still trying to access rows, even if it could possibly be NULL.
the other possibility lies in how you've defined rows. I'm guessing it's a property in your class. But where you have rows=[dict objectForKey:#"users"];, rows can point to nothing after the method's finished. Rows will still have the address of where [dict objectForKey:] was, but after the scope of the method, dict may be gone and all the data that comes with it.
NSDictionary * dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:&error];
under the KVC guidelines, you should expect dict to autorelease after the end of method.
and another possibility is, since i don't know the specifics of the JSON class you're using, is that when you release jsonreturn, you're also dealloc'ing all the data associated with it. So in effect, rows is pointing to nothing.
case in point, the error seems to be rooted in how you're setting/retaining/accessing rows.
try using the Build->Build&Analyze in xcode. it might give you some more hints. or throw in a bunch of NSLog(#"%d",[rows count]); all over. also try using the debugger. it'll give you a trace of method calls that lead up to [rows count] faulting.