I have a class that gets data from a repository and stuffs it into an NSArray:
EpisodeRepository Class
-(NSMutableArray *)getEpisodes {
NSMutableArray *episodes = [NSMutableArray array];
NSData *data = [self getDataFromAction:kGetEpisodesAction];
NSError *error = nil;
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
for (NSDictionary *d in json) {
Episode *episode = [self buildEpisodeFromJSONDictionary:d];
[episodes addObject:episode];
}
return episodes;
}
In my view controller, I keep a reference to the repo and the array of episodes, like so:
#interface VideoController : UITableViewController
#property (nonatomic, strong) NSArray *episodes;
#property (nonatomic, strong) EpisodeRepository *episodeRepo;
In the implementation, what I want to happen is that every the view appears, refetch the data from the web services(in the EpisodeRepository) and reload the table view with the updated episodes. This works great the first time the tableview is populated. The problem is that when I call, cellForRowAtIndexPath, the episodes array is completely blank. It's not null, but there is no data whatsoever in it. The weird thing is that all the other tableView delegates realize that there is data in the array and act accordingly. What is special about cellForRowAtIndexPath and why would it possibly delete my entries in the array?
Here is the controller implementation:
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self) {
episodeRepo = [[EpisodeRepository alloc] init];
}
return self;
}
-(void)viewWillAppear:(BOOL)animated
{
[self loadContent];
[self.tableView reloadData];
[super viewWillAppear:animated];
}
-(void) loadContent {
self.episodes = [self.episodeRepo getEpisodes];
self.seasons = [self getSeasons:self.episodes];
[self setUILoaded];
}
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//episodes is an empty array here???
}
I found the solution! It turns out I had code in my App Delegate under the willSelectViewController for the tab bar that was allocating the array outside of the view controller. Lesson learned: don't do this!
Related
I have done the following task of taking data from a csv file and publishing it in a table view. What i would like to do is use uisegmentented control to sort and filter the core data which i have.
I have a list of names somewhat like this and i have it stored in the data model as firstname, lastname and gender. I have designed the following screenshot. By clicking on the a-z button or z-a button. The data should sort and filter according to gender like male,female or both.
I am new to core data(sorting and filtering) and UISegmented controls. Could you please help me in doing the above tasks in detail and how you have done it.
Following is the code for the work i have done till now. And i am using xib's.
RootViewController.h
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#interface RootViewController : UITableViewController
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (retain) NSArray *people;
//! Filtering view from XIB file
#property (nonatomic, retain) IBOutlet UIView *filterSortView;
//! Filter control
#property (nonatomic, retain) IBOutlet UISegmentedControl *filterControl;
//! Sort control
#property (nonatomic, retain) IBOutlet UISegmentedControl *sortControl;
#end
Then we RootViewController.m
#import "RootViewController.h"
#import "Person.h"
#implementation RootViewController
#synthesize people;
#synthesize managedObjectContext=__managedObjectContext;
// Sorting & filtering UI components
#synthesize filterSortView, filterControl, sortControl;
#pragma mark -
#pragma mark View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.title = #"People";
// Get our people array - this next block of code could probably be extracted out to a private
// method and generalized for different fetch request types
NSEntityDescription *personEntity = [NSEntityDescription entityForName:#"Person"
inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:personEntity];
NSError *error = nil;
self.people = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error)
{
[NSException raise:NSInternalInconsistencyException format:#"Could not fetch Core Data records: %#",error];
}
[request release];
// Now do a secondary load from another XIB file (other than the main table view)
[[NSBundle mainBundle] loadNibNamed:#"FilterSortView" owner:self options:nil];
// This disables the default selection
self.filterControl.selectedSegmentIndex = -1;
self.sortControl.selectedSegmentIndex = -1;
// Now register for events when the value changes
// TODO: write method implementations for each of these and then uncomment these lines.
// [self.filterControl addTarget:self action:#selector( -- TODO -- ) forControlEvents:UIControlEventValueChanged];
// [self.sortControl addTarget:self action:#selector( -- TODO -- ) forControlEvents:UIControlEventValueChanged];
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.filterControl = nil;
self.sortControl = nil;
self.filterSortView = nil;
}
#pragma mark -
#pragma mark UITableViewDataSource
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.people count];
}
#pragma mark -
#pragma mark UITableViewDelegate
// 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:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Person *person = [self.people objectAtIndex:[indexPath row]];
cell.textLabel.text = [NSString stringWithFormat:#"%#, %#",person.lastName,person.firstName];
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return self.filterSortView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return self.filterSortView.frame.size.height;
}
#pragma mark -
#pragma mark Class Plumbing
- (void)dealloc
{
[filterControl release];
[sortControl release];
[filterSortView release];
[people release];
[__managedObjectContext release];
[super dealloc];
}
#end
Then we Have FilteredListAppDelegate.h
#import <UIKit/UIKit.h>
#interface FilteredListAppDelegate : NSObject <UIApplicationDelegate> {
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
Then we have FilteredListAppDelegate.m
#import "FilteredListAppDelegate.h"
#import "RootViewController.h"
#import "Person.h"
// Private methods
#interface FilteredListAppDelegate ()
- (BOOL) _populateCoreData;
#end
#implementation FilteredListAppDelegate
#synthesize window=_window;
#synthesize managedObjectContext=__managedObjectContext;
#synthesize managedObjectModel=__managedObjectModel;
#synthesize persistentStoreCoordinator=__persistentStoreCoordinator;
#synthesize navigationController=_navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Prepopulate Core Data with our data, if necessary.
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
if ([settings objectForKey:#"core_data_populated"] == nil)
{
BOOL success = [self _populateCoreData];
if (success)
{
[settings setValue:[NSNumber numberWithBool:YES] forKey:#"core_data_populated"];
}
}
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
/*
Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
*/
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Saves changes in the application's managed object context before the application terminates.
[self saveContext];
}
- (void)dealloc
{
[_window release];
[__managedObjectContext release];
[__managedObjectModel release];
[__persistentStoreCoordinator release];
[_navigationController release];
[super dealloc];
}
- (void)awakeFromNib
{
RootViewController *rootViewController = (RootViewController *)[self.navigationController topViewController];
rootViewController.managedObjectContext = self.managedObjectContext;
}
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
#pragma mark - Private Methods
/**
* Hacky method to just throw a bunch of data into Core Data.
*/
- (BOOL) _populateCoreData
{
NSManagedObjectContext *context = [self managedObjectContext];
// Get the names out of the text file
NSError *error = nil;
NSString *filename = [[NSBundle mainBundle] pathForResource:#"actors_by_gender" ofType:#"csv"];
NSString *fileContents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&error];
if (error)
{
[NSException raise:NSInternalInconsistencyException format:#"Unable to read text file for adding to Core Data"];
}
NSArray *namesChoppedByNewline = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *singleNameRecord in namesChoppedByNewline)
{
NSArray *attributesChoppedByComma = [singleNameRecord componentsSeparatedByString:#","];
// Now make each record a new Core Data object
Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
newPerson.firstName = [attributesChoppedByComma objectAtIndex:0];
newPerson.lastName = [attributesChoppedByComma objectAtIndex:1];
newPerson.gender = [attributesChoppedByComma objectAtIndex:2];
}
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
[NSException raise:NSInternalInconsistencyException
format:#"Couldn't save Core Data data for Person entity import. Reason: %#",error];
}
return YES;
}
#pragma mark - Core Data stack
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from the application's model.
*/
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil)
{
return __managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"FilteredList" withExtension:#"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"FilteredList.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
#pragma mark - Application's Documents directory
/**
Returns the URL to the application's Documents directory.
*/
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
#end
Thank you for the help.
You need to create and set an instance of NSSortDescriptor on your fetch request before you execute it.
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES];
By switching the ascending parameter based on your UISegmentedControl state, you can easily return the results in a-z or z-a order.
Alternatively, you could apply the same sort descriptor to your array of people using sortedArrayUsingDescriptors like so:
NSArray *array = [[self people] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
Keep in mind however that as you toggle the segmented control, you will either need to perform your fetch request again, or sort your array into a new array (perhaps use NSMutableArray instead).
To implement your segmented control, you will need to add a target and selector:
[[self segmentedControl] addTarget:self action:#selector(segmentedControlEventHandler:) forControlEvents:UIControlEventValueChanged];
Then use the following method to amend and update your sort descriptor and table view.
- (void)segmentedControlEventHandler:(id)sender
{
UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
if([segmentedControl selectedSegmentIndex] == 0)
{
//segment 1 selected
//set BOOL value for ascending sort descriptor
//update table view -> [[self tableView] reloadData];
}
else
{
//segment 2 selected
}
}
to filter by gender: NSPredicate *predicatem =[NSPredicate predicateWithFormat:#"gender == %#", #"m" ];
//give the predicate to the fecth request
request.predicate=predicatem;
I have this code:
// .m
- (void)viewDidLoad {
NSMutableArray *array = [[NSMutableArray alloc] init];
[self addToArray];
}
- (void)addToArray {
NSString *stringA;
[stringA isEqualToString:#"door"];
NSString *stringB;
[stringB isEqualToString:textField.text];
[array addObject:stringA];
if ([stringA isEqual:stringB]) {
[stringA isEqual:nil];
[tableView reloadData];
} else {
[array addObject:stringB];
[tableView reloadData];
}
}
When I call the method addToArray it keeps returning me an error called Thread 1: Program recived signal "EXC_BAD_ACCESS", and the debugger output says : Single stepping until exit from function objc_msgSend, which has no line number information. at the line [self addToArray]. Any idea of how to solve it? I have wasted to much time with it, please help me!
As was said by others, array should be an instance variable or property of the class, declared in the .h file:
#property (strong) NSMutableArray *array;
Or, without ARC:
#property (retain) NSMutableArray *array;
Now you #synthesize array; in your implementation file and can access it from anywhere. Then you can do:
- (void) viewDidLoad
{
self.array = [[NSMutableArray alloc] init];
[self addToArray];
}
You seem to assume that isEqualToString does an assignment. It doesn't, it checks strings for (textual) equality. Try this:
- (void) addToArray
{
NSString *stringA = #"door";
NSString *stringB = textField.text;
[array addObject: stringA];
if (![stringA isEqualToString: stringB])
[array addObject: stringB];
[tableView reloadData];
}
These two variables are uninitialized and will cause you big problems:
NSString *stringA;
[stringA isEqualToString:#"door"];
NSString *stringB;
[stringB isEqualToString:textField.text];
You have not assigned anything to either stringA or stringB. Besides the result of your call to isEqualToString is never used.
Two things I can notice in your code:
1) Make array a class variable, so you can access it from your -[addToArray] method. Better do this in your .h file, for example:
#interface MyViewController : UIViewController {
#private
// ...skipped...
NSMutableArray * array;
// ...rest of class skipped...
}
#end
Then, in your .m file the method should look like this:
// .m
- (void)viewDidLoad {
array = [[NSMutableArray alloc] init];
[self addToArray];
}
And don't forget to release the array:
- (void)dealloc {
[array release];
[super dealloc];
}
2) Do not mess up -[NSString isEqualToString:] method with simple assigment to a variable. So in your -[addToArray] method, for example, replace this:
NSString *stringA;
[stringA isEqualToString:#"door"];
with this:
NSString *stringA = #"door";
And this:
NSString *stringB;
[stringB isEqualToString:textField.text];
with this:
NSString *stringB = textField.text;
3) Check the logic of -[addToArray] method - it is not very clear what are you going achieve.
I am parsing some JSON from the internet and then adding them to an array which is the datasource for my UITableView. I am not sure when I should be releasing my array?
.h: items
#property(nonatomic,retain)NSMutableArray* items;
.m: connectionDidFinishLoading
// fetch succeeded
NSString* json_string = [[NSString alloc] initWithData:retrievedData encoding:NSUTF8StringEncoding];
//Check ST status
int status = [[[[json_string objectFromJSONString] valueForKey:#"response"] valueForKey:#"status"]intValue];
//NSLog(#"Status: %d", status);
items = [[NSMutableArray alloc] init];
NSDictionary* messages = [[NSDictionary alloc] init];
switch (status) {
case 200:
messages = [[[json_string objectFromJSONString] valueForKey:#"messages"] valueForKey:#"message"];
for (NSDictionary *message in messages)
{
[items addObject:message];
}
[self.tableView reloadData];
break;
default:
break;
}
One, you might want to declare items as an instance of NSMutableArray if you intend to call addObject: on it.
Two, declare it as a property so that if you end up getting it multiple times the older value will be released when you do.
self.items = [NSMutableArray array];
And the correct point of releasing it would be dealloc.
Probably you don't want to release it immediately if you:
use didSelectRowAtIndexPath: method for detail views and pass this data to them
define custom UITableViewCell styles in cellForRowAtIndexPath: method
use this data elsewhere
Best practice is declare an instance variable and synthesize it in .m, use in appropriate operations and release in dealloc method.
One possible release point that you could use is where you refresh your data that shown on table.
Example:
I get dictionaries in an array from an API in my app and use something like that.
MyTableViewController.h
#interface MyTableViewController {
NSMutableArray *items;
}
#property (nonatomic, retain) NSMutableArray *items;
#end
MyTableViewController.m
#implementation MyTableViewController
#synthesize items;
- (void)dealloc
{
[items release];
[super dealloc];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"FilesCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
cell.textLabel.text = [[items objectAtIndex:indexPath.row] valueForKey:#"name"];
cell.imageView.image = [UIImage imageNamed:[[NSString alloc] initWithFormat:#"filetype_%#.png", [[items objectAtIndex:indexPath.row] valueForKey:#"type"]]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyDetailViewController *detailViewController = [[MyDetailViewController alloc] initWithNibName:#"MyDetailViewController" bundle:[NSBundle mainBundle]];
detailViewController.item = [items objectAtIndex:indexPath.row];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
detailViewController = nil;
}
}
- (void)getItems
{
[items release];
items = [[NSMutableArray alloc] init];
//Do some requests here
for (NSDictionary *dict in results)
{
[items insertObject:dict atIndex:0];
}
[self.tableView reloadData];
[self stopLoading];
}
#end
Releasing at wrong places some time lead to memory leaks, before allocation itself u can have a condition like if() { [...release]}.Not tested but this kind of release avoid leaks.
The most common is to have the items variable as an attribute of your class, once you will probably need it to use in your tableView:cellForRowAtIndexPath: method.
So, having it as an attribute variable you can release it on the dealloc method.
It's clear that your array item will be used by UITableView to show data.
First declare it as instance variable in your .h class.
.h class
#interface MyClass
{
MSMutableArray* items;
}
#property(nonatomic,retain) MSMutableArray* items;
#end
In your .m class.
#synthesis iMyArray;
And you code for filling the array should be
NSMutabelArray* itemsTemp = [[NSMutabelArray alloc] initWithCapacity:1];
messages = [[[json_string objectFromJSONString] valueForKey:#"messages"] valueForKey:#"message"];
[json_string release];
for (NSDictionary *message in messages) {
NSLog(#"%#",[message valueForKey:#"body"]);
[itemsTemp addObject:message];
}
self.items= itemsTemp;
[itemsTemp release];
itemsTemp = nil;
[self.tableView reloadData];
Now in dealloc release your array instance.
-(void) dealloc
{
if(items )
{
[items release];
items = nil ;
}
[super dealloc];
}
Proper way is make it property in .h class, since you have declared it as property: remember one thing always alloc a property by using self.
your statement items=[[NSMutableArray alloc] init];
is wrong.(use self) also since your property is retain type the using alloc on it increase retain count.that gives you a leak.
so use in this way in viewDidLoad
NSMutableArray *tempArray=[[NSMutableArray alloc] init];
self.items=tempArray;
[tempArray release];
then release your items array in dealloc and set it nil in viewDidUnload
- (void)viewDidUnload {
[super viewDidUnload];
self.items=nil;
}
- (void)dealloc {
[self.items release];
[super dealloc];
}
Hope now you can understand how you should use this.
According to Apple's documentation of UITableView reloadData method:
"[...] For efficiency, the table view redisplays only those rows that are visible"
That means yo should not release the items array as long as the table is being used, i.e. you have to declare the array as a property.
First because if you scroll the view, you will still need the items information to display the rows below or above.
And second, because by being a property you ensure that a previous value is going to be released if you happen to assign a new value to items.
Finally, the common place to release a property is in the dealloc method and depending on your implementation in viewDidUnload method.
Update: Unfortunately the help offered below did not solve my problem of sharing a class property across functions. Can anyone else suggest a possible problem? Here's the latest code:
Header .h:
#interface FirstViewController:UIViewController <UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate> {
NSDictionary *sectorDictionary;
NSInteger sectorCount;
}
#property (nonatomic, retain) NSDictionary *sectorDictionary;
- (id)initWithData:(NSMutableDictionary*)inData;
Implementation .m:
#synthesize sectorDictionary;
- (id) testFunction:(NSDictionary*)dictionary {
NSLog(#"Count #1: %d", [dictionary count]);
return nil;
}
- (id)initWithData:(NSMutableDictionary *)inData {
self = [self init];
if (self) {
[self testFunction:inData];
// set the retained property
self.sectorDictionary = inData;
}
return self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Count #2: %d", [self.sectorDictionaryCopy count]);
return [self.sectorDictionaryCopy count];
}
Console output:
2010-05-05 20:11:13.126 JSONApp[17378:207] Count #1: 9
2010-05-05 20:11:13.130 JSONApp[17378:207] Count #2: 0
Following up on this question about sharing an object between classes, I now need to figure out how to share that object across various functions in a class.
First, the setup: In my App Delegate I load menu information from JSON into a NSMutableDictionary and message that through to a view controller using a function called initWithData. I need to use this dictionary to populate a new Table View, which has methods like numberOfRowsInSection and cellForRowAtIndexPath.
I'd like to use the dictionary count to return numberOfRowsInSection and info in the dictionary to populate each cell. Unfortunately, my code never gets beyond the init stage and the dictionary is empty so numberOfRowsInSection always returns zero.
I thought I could create a class property, synthesize it and then set it. But it doesn't seem to want to retain the property's value. What am I doing wrong here?
In the header .h:
#interface FirstViewController:UIViewController <UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate> {
NSMutableDictionary *sectorDictionary;
NSInteger sectorCount;
}
#property (nonatomic, retain) NSMutableDictionary *sectorDictionary;
- (id)initWithData:(NSMutableDictionary*)data;
#end
in the implementation .m:
#synthesize sectorDictionary;
- (id) testFunction:(NSMutableDictionary*)dictionary {
NSLog(#"Count #1: %d", [dictionary count]);
return nil;
}
- (id)initWithData:(NSMutableDictionary *)data {
if (!(self=[super init])) {
return nil;
}
[self testFunction:data];
// this is where I'd like to set a retained property
self.sectorDictionary = data;
return nil;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Count #2: %d", [self.sectorDictionary count]);
return [self.sectorDictionary count];
}
Output from NSLog:
2010-05-04 23:00:06.255 JSONApp[15890:207] Count #1: 9
2010-05-04 23:00:06.259 JSONApp[15890:207] Count #2: 0
Any init function should return self if it completes successfully. Change the return nil to return self at the end of initWithData.
-(id) initWithData:(NSMutableDictionary *)inData {
self = [self init]; // best practice use super for method with same name
if ( self ) {
self.sectorDictionary = inData;
}
return self;
}
The sectorDictionary member should not be mutable. If it changes, you need to call reloadData on the table. Your setter function should make an immutable copy.
-(void) setSectorDictionary:(NSDictionary *)inData {
NSDictionary *old = sectorDictionary;
sectorDicitonary = inData ? [[NSDictionary alloc] intWithDictionary:inData] : nil;
[self.table reloadData];
[old release];
}
I believe your problem lies within the line self.sectorDictionary = data.
You need to firstly allocate some memory for the dictionary you are creating, so a line like
self.sectorDictionary = [[NSMutableDictionary alloc] init]; is going to be necessary.
After you have initiated the new dictionary you need to populate it with the contents of the dictionary that was passed in.
So...
Try the line:
self.sectorDictionary = [[NSMutableDictionary alloc] initWithDictionary:data];
instead of:
self.sectorDictionary = data;
instead of
self.sectorDictionary = data;
try this way...
self.sectorDictionary = [[NSMutableDictionary alloc] init];
self.sectorDictionary = (NSMutableDictionary *) data;
Ok, I'm trying to avoid global variables, so I read up on singleton classes.
This is a try to set and read a mutable array, but the result is null.
//Content.h
#interface Content : NSObject {
NSMutableArray *contentArray;
}
+ (Content *) sharedInstance;
- (NSMutableArray *) getArray;
- (void) addArray:(NSMutableArray *)mutableArray;
#end
.
//Content.m
#implementation Content
static Content *_sharedInstance;
+ (Content *) sharedInstance
{
if (!_sharedInstance)
{
_sharedInstance = [[Content alloc] init];
}
return _sharedInstance;
}
- (NSMutableArray *) getArray{
return contentArray;
}
- (void) addArray:(NSMutableArray *)mutableArray{
[contentArray addObject:mutableArray];
}
#end
And in a ViewController I added #import "Content.h", where I try to call this:
NSMutableArray *mArray = [NSMutableArray arrayWithObjects:#"test",#"foo",#"bar",nil];
Content *content = [Content sharedInstance];
[content addArray:mArray];
NSLog(#"contentArray: %#", [content getArray]);
You need to alloc and init the array first. Personally I'd do it in the init method of the content class like so:
-(id)init{
if(self = [super init]){
…the rest of your init code…
contentArray = [[NSMutableArray alloc] init];
}
return self;
}
You never actually alloc/initialise the contentArray array.