NSMutableArray Not Retaining Objects Outside Method [closed] - iphone

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
In my -viewDidLoad method, I initialize many NSMutableDictionaries, and add them to an initialized NSMutableArray declared via #property in the class header file. The relevant code is shown below. In short, I'm webscraping information from an HTML webpage.
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
_regionalDicts = [[NSMutableArray alloc] init];
for (int i = 0; i < [strings count]; i++) {
NSString *str = [strings objectAtIndex:i];
//Property parser:
if ([str rangeOfString:#"<td>"].location != NSNotFound) {
NSString *parsedTD1 = [str stringByReplacingOccurrencesOfString:#"<td>" withString:#""];
NSString *parsedTD2 = [parsedTD1 stringByReplacingOccurrencesOfString:#"</td>" withString:#""];
NSString *parsedTD3 = [parsedTD2 stringByReplacingOccurrencesOfString:#" " withString:#"\n"];
NSString *final = [parsedTD3 stringByReplacingOccurrencesOfString:#"\t" withString:#""];
//NSLog(#"Final string: %#", final);
if ([final isEqualToString:#""]) {
continue;
}
if (gotEventType == NO) {
gotEventType = YES;
[dict setObject:final forKey:#"type"];
continue;
}
if (gotRegional == YES && gotLocation == NO) {
gotLocation = YES;
[dict setObject:final forKey:#"location"];
continue;
}
if (gotLocation == YES && gotCity == NO) {
gotCity = YES;
NSString *cityToReturn = [final stringByReplacingOccurrencesOfString:#"\n" withString:#""];
[dict setObject:cityToReturn forKey:#"city"];
continue;
}
if (gotRegional == YES && gotEventType == YES && gotCity == YES && gotLocation == YES && gotURL == YES) {
gotRegional = NO;
gotEventType = NO;
gotCity = NO;
gotLocation = NO;
gotURL = NO;
NSLog(#"Regional: %#", [dict objectForKey:#"regional"]);
NSLog(#"Type: %#", [dict objectForKey:#"type"]);
NSLog(#"City: %#", [dict objectForKey:#"city"]);
//Testing to see if anything is nil
NSLog(#"Location: %#\n", [dict objectForKey:#"location"]);
if (!_regionalDicts) {
NSLog(#"Dict is nil");
}
[_regionalDicts addObject:dict];
NSLog(#"Objects in array: %u", [_regionalDicts count]);
NSMutableDictionary *tempDict = [_regionalDicts objectAtIndex:[_regionalDicts count]-1];
NSLog(#"Regional in array: %#", [tempDict objectForKey:#"regional"]);
[dict removeAllObjects];
continue;
}
It's clear that the generated dictionaries are generated and retained within the _regionalDicts mutable array, which is declared in the header file like this:
#property (strong, nonatomic) IBOutlet NSMutableArray *regionalDicts;
However, when I attempt to pass in information to table view cells in in the same class, the dictionaries' contents are null. There are as many objects within the array as dictionaries I am expecting, but they do not contain any content.
- (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];
}
if (_regionalDicts) {
NSMutableDictionary *dict = [_regionalDicts objectAtIndex:0];
NSLog(#"Setting label %#", [dict objectForKey:#"city"]);
[cell.textLabel setText:[dict objectForKey:#"regional"]];
}
return cell;
}
Returns:
2013-04-01 19:58:50.250 MatchScrape[53570:207] Setting label (null)
I can only imagine that a memory management issue is to blame. Why would the contents of a class array be nullified when accessed outside the scope of the method they are added in, but allow the array to retain the same count?

You seem to believe that adding the dictionary to the array doesn't actually add the dictionary to the array, but instead adds a copy of the dictionary. You're probably thinking of how it might work in a language like C++ — but that isn't how it works here. Remember, Objective-C objects are always accessed by reference: you never directly store the object itself in a variable or array — you're just shuffling around a pointer to the actual object, which usually lives on the heap.
So when you add _dict to the array, the one in the array is the very same object referenced by _dict. Anything you do to that dictionary — no matter what reference you use — will be reflected everywhere else that dictionary is referenced, because it's the same dictionary. You haven't made a copy of it. Thus, when you do [_dict removeAllObjects], that removes all the objects from the dictionary and you end up with an array that contains the same empty dictionary a bunch of times.

Related

Objective-C - unidentified Error in for Loop

the following code attempts to search for a String given by a cell.textlabel.text in a CSV-File
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//create singleton instance
Globals *myGlobals = [Globals sharedGlobals];
//get searchstring form cell
NSString *stringToFind = [self.tableView cellForRowAtIndexPath:indexPath].textLabel.text;
//get Path of csv and write data in string:allLines
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"ITILcsv" ofType:#"txt"];
if(filePath){
NSString *wholeCSV = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSArray *allLines = [wholeCSV componentsSeparatedByString:#"\n"];
//declaration
NSArray *currentArray = nil;
NSString *currentSearchString = nil;
//look for searchstring in 4th line of csv, if found write whole line to a singleton-variable
for (int i=0 ; i < [allLines count]; i++){
currentArray = [[allLines objectAtIndex:i] componentsSeparatedByString:#";"];
currentSearchString = [currentArray objectAtIndex:3];
if ([stringToFind isEqualToString:currentSearchString]){
[myGlobals setCurrentLine:currentArray];
}
}
}
Working quite a bit with csv-files in my current project I'm pretty sure this should work, but somehow the app always crashes when the function is called.
Through a whole bunch of testing I'm pretty sure that the problem is in the following lines:
currentArray = [[allLines objectAtIndex:i] componentsSeparatedByString:#";"];
currentSearchString = [currentArray objectAtIndex:3];
The program works with these two lines commented out, but doesnt implement the desired function ;)
I have no clue what the problem might be?
The Error is a SIGABRT in "main".
Thanks in advance everybody.
Might crash when your currentArray has elements less than 3 and you are referring index 3. So in that case your finding for an index which is out of reach.
So better approach would be
for (int i=0 ; i < [allLines count]; i++)
{
currentArray = [[allLines objectAtIndex:i] componentsSeparatedByString:#";"];
// check and then pick
if ([currentArray count] > 3)
{
currentSearchString = [currentArray objectAtIndex:3];
if ([stringToFind isEqualToString:currentSearchString])
{
[myGlobals setCurrentLine:currentArray];
}
}
}

tableview will not update on phone

If this is a repost, I apologize, but I have been scouring the net and cant seem to find anything that works. I have a list of workouts that I display in a tableview that are gathered in plists in the bundle. There is a also a separate tab that I have that allows a user to build their own workouts and save them in the documents folder plist file. Once they are saved, they are added to the table view. In the simulator, everyuhting works fine. But on the actual device, it is not updated unless I close the program for an extended period of time, reload the program from xcode, or turn the phone off. I have tried adding [[self tableview] reload] to "viewDidLoad", "viewWillappear", and "viewDidAppear" and none of them work. Once again, the file is saved, the updating does work in the simulator, and it doesn't update in the phone right away. Any suggestions? Thanks.
Edit: i know it is a long piece of code, but should be straight forward (hopefully lol)
#import "BIDWODList.h"
#import "BIDWODDetails.h"
#define kFileName #"SavedDFWorkouts.plist"
#interface BIDWODList ()
#end
#implementation BIDWODList
#synthesize names;
#synthesize savedNames;
#synthesize keys;
#synthesize details;
#synthesize wodType;
#synthesize benchmarkGirls;
#synthesize theNewGirls;
#synthesize heroes;
#synthesize savedDFGWorkouts;
#synthesize chosenWOD;
#synthesize chosenDetails;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *buildBenchmarkGirls = [[NSMutableArray alloc] init];
NSMutableArray *buildTheNewGirls = [[NSMutableArray alloc] init];
NSMutableArray *buildHeroes = [[NSMutableArray alloc] init];
NSBundle *bundle = [NSBundle mainBundle];
NSURL *plistURL = [bundle URLForResource:#"CrossfitWOD" withExtension:#"plist"];
//put the contents of the plist into a NSDictionary, and then into names instance variable
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:plistURL];
self.names = dictionary;
//take all the keys in the dictionary and make an array out of those key names
self.keys = [self.names allKeys];
for (NSString *nameCheck in keys){
self.details = [names valueForKey:nameCheck];
if ([[self.details valueForKey:#"Type"] isEqualToString:#"The Benchmark Girls"]) {
[buildBenchmarkGirls addObject:nameCheck];
}else if ([[self.details valueForKey:#"Type"] isEqualToString:#"The New Girls"]) {
[buildTheNewGirls addObject:nameCheck];
}else {
[buildHeroes addObject:nameCheck];
}
}
NSString *filePath = [self dataFilePath];
NSMutableDictionary *savedWorkout = [[NSMutableDictionary alloc]initWithContentsOfFile:filePath];
self.savedNames = savedWorkout;
self.savedDFGWorkouts = [[savedWorkout allKeys] sortedArrayUsingSelector:#selector(compare:)];
self.benchmarkGirls = [buildBenchmarkGirls sortedArrayUsingSelector:#selector(compare:)];
self.theNewGirls = [buildTheNewGirls sortedArrayUsingSelector:#selector(compare:)];
self.heroes = [buildHeroes sortedArrayUsingSelector:#selector(compare:)];
//[[self tableView] reloadData]; //reloads the data in case a DFG workout was saved
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.names = nil;
self.keys = nil;
self.benchmarkGirls = nil;
self.theNewGirls = nil;;
self.heroes = nil;
self.details = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (NSString *)dataFilePath {
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:kFileName];
}
-(void)viewDidAppear:(BOOL)animated{
[[self tableView] reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 4;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (section == 0) {
return [benchmarkGirls count];
}else if (section == 1){
return [theNewGirls count];
}else if (section == 2){
return [heroes count];
}else{
return [savedDFGWorkouts count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
static NSString *SectionsTableIdentifier = #"SectionsTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SectionsTableIdentifier];
}
if (section == 0) {
cell.textLabel.text = [benchmarkGirls objectAtIndex:row];
}else if (section == 1) {
cell.textLabel.text = [theNewGirls objectAtIndex:row];
}else if (section == 2) {
cell.textLabel.text = [heroes objectAtIndex:row];
}else{
cell.textLabel.text = [savedDFGWorkouts objectAtIndex:row];
}
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (section == 0) {
return #" The Benchmark Girls";
}else if (section == 1){
return #"The New Girls";
}else if (section ==2){
return #"The Heroes";
}else{
return #"Saved DFG Workouts";
}
}
#pragma mark - Table view delegate
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
BIDWODDetails *destination = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
if (section == 0) {
self.chosenWOD = [self.benchmarkGirls objectAtIndex:row];
self.chosenDetails = [names objectForKey:chosenWOD];
}else if (section == 1) {
self.chosenWOD = [self.theNewGirls objectAtIndex:row];
self.chosenDetails = [names objectForKey:chosenWOD];
}else if (section ==2) {
self.chosenWOD = [self.heroes objectAtIndex:row];
self.chosenDetails = [names objectForKey:chosenWOD];
}else {
self.chosenWOD = [self.savedDFGWorkouts objectAtIndex:row];
self.chosenDetails = [savedNames objectForKey:chosenWOD];
}//end if
//self.chosenDetails = [names objectForKey:chosenWOD];
//[destination setValue:chosenWOD forKey:#"chosenWOD"];
//[destination setValue:chosenDetails forKey:#"chosenDetails"];
destination.chosenWOD = self.chosenWOD;
destination.chosenDetails = self.chosenDetails;
}
#end
Different behaviour between the simulator and the device is often related to incorrect case being used in filenames - the simulator isn't case sensitive, and the device is. Check that you have the correct case used everywhere you reference the plist file.
Alternatively, on the simulator you are able to write directly to the application bundle, but on the device this is not possible and you can only write to certain directories in your application's sandbox, typically the documents directory. You would normally copy a plist to the documents directory on first run, and use that file thereafter.
If I understand right your code you load plist file only in viewDidLoad, but most likely this function called only when you first time load your view. To make it work you should load plist
in viewDidAppear. Something like this:
- (void)viewDidAppear {
NSBundle *bundle = [NSBundle mainBundle];
NSURL *plistURL = [bundle URLForResource:#"CrossfitWOD" withExtension:#"plist"];
//put the contents of the plist into a NSDictionary, and then into names instance variable
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:plistURL];
self.names = dictionary;
//take all the keys in the dictionary and make an array out of those key names
self.keys = [self.names allKeys];
for (NSString *nameCheck in keys){
self.details = [names valueForKey:nameCheck];
if ([[self.details valueForKey:#"Type"] isEqualToString:#"The Benchmark Girls"]) {
[buildBenchmarkGirls addObject:nameCheck];
}else if ([[self.details valueForKey:#"Type"] isEqualToString:#"The New Girls"]) {
[buildTheNewGirls addObject:nameCheck];
}else {
[buildHeroes addObject:nameCheck];
}
}
NSString *filePath = [self dataFilePath];
NSMutableDictionary *savedWorkout = [[NSMutableDictionary alloc]initWithContentsOfFile:filePath];
self.savedNames = savedWorkout;
self.savedDFGWorkouts = [[savedWorkout allKeys] sortedArrayUsingSelector:#selector(compare:)];
[self.tableView reloadData];
}
If it works in the Simulator and does not on the phone, almost for sure the problem is a timing issue. Saving files on a real phone takes much longer than on the simulator.
You should do the following:
when you save a file, log it, and log the return code from the save. If the way you save does not provide a return code, use NSFileManager to verify the file is in fact where it should be and even the size of it. This takes time to do but you should do it.
when your table is asking for the number of this and that, log it, and lot what is returned. You may find that that this comes before the files are saved.
It takes time and effort, but if you start logging all relevant things, you can find it. I just spend 6 hours today tracking down a race condition I had thought could never happen, and it was only after looking at a huge trail of messages that I was able to see the problem.
Almost for sure you will see that either file is not saved, its not where you thought it was, or that the phone timing means some events happen later than they do in the Simulator.

App crashing when scrolling TableView

My app is crashing when i scroll my TableView. First in my viewDidLoad method a load a dictionary from a file and for this dictionary i enumerate all keys.
- (void)viewDidLoad {
[super viewDidLoad];
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
path = [rootPath stringByAppendingPathComponent:[NSString stringWithFormat:#"currency.archive"]];
banks = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
keys = [banks allKeys];
// set date for last update
dayMonthYear.text = [banks objectForKey:#"Last Updated"];
}
In my cellForRowAtIndexPath i populate cells with data from that dictionary. Anyway when my app starts everything looks fine, first five rows are drawn correctly, but when i start to scroll my app crash. My idea is that the problem is with autoreleased object here, i tried to retain them and after using them to release ,but unsuccessful. DEBUGGER SHOWS THAT MY PROBLEM IS AT LINE WITH BOLD
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell %d_%d",indexPath.section,indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CurrencyTableCell" owner:self options:nil];
cell = currencyTableCell;
//don't show selected cell
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//set height
self.cellHeight = cell.frame.size.height;
}
// Fetch currency
NSString *currentCurrency = [keys objectAtIndex:indexPath.row];
NSDictionary *fetchedCurrency = [banks objectForKey:currentCurrency];
**NSString *name = [fetchedCurrency objectForKey:#"Currency Name"];**
currencyTitle.text = name;
NSString *charCode = [fetchedCurrency objectForKey:#"Code"];
currencyCode.text = charCode;
NSString* formattedNumber = [NSString stringWithFormat:#"%.02f",[[fetchedCurrency objectForKey:#"Value"] floatValue]];
if ([formattedNumber length] == 4) {
formattedNumber = [NSString stringWithFormat:#"%#%#",#"0",formattedNumber];
}
buyPrice.text = formattedNumber;
return cell;
}
As a result from the discussion, [banks objectForKey:#"Last Updated"] gives you a NSString, not a NSDictionary!
You could get around this error by doing
if ([[banks objectForKey:currentCurrency] class] == [NSDictionary class]) {
... rest of the code here ..
}
Change your viewDidLoad with below code it will work
- (void)viewDidLoad {
[super viewDidLoad];
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
path = [rootPath stringByAppendingPathComponent:[NSString stringWithFormat:#"currency.archive"]];
banks = [[NSDictionary alloc] initWithDictionary:[NSKeyedUnarchiver unarchiveObjectWithFile:path]];
keys = [[NSArray alloc] initWithArray:[banks allKeys]];
// set date for last update
dayMonthYear.text = [banks objectForKey:#"Last Updated"];
}
-[NSCFString objectForKey:]: unrecognized selector sent to instance
0x4bab9c0
Your banks and keys variables aren't retained, as mentioned in another answer, but this isn't the error.
As per this error, your fetchedCurrency object is an NSString, not an NSDictionary. Check the format of your currency.archive file.

NSMutableArray, pList, Tableview muddle and meltdown

I have a preferences view which shows a different table view depending on which Segmented Control is clicked.
I hard coded some NSMutableArrays to test basic principles:
prefsIssuesList = [[NSMutableArray alloc] init];
[prefsIssuesList addObject:#"Governance"];
[prefsIssuesList addObject:#"Innovation and technology"];
...etc
prefsIndustriesList = [[NSMutableArray alloc] init];
[prefsIndustriesList addObject:#"Aerospace and defence"];
... etc
prefsServicesList = [[NSMutableArray alloc] init];
[prefsServicesList addObject:#"Audit and assurance"];
...etc
currentArray = [[NSMutableArray alloc] init];
currentArray = self.prefsIssuesList;
Then reload the tableview with currentArray, adding a UITableViewCellAccessoryCheckmark.
Everything works fine.
But now I want to store wether the checkmark is on or off in a pList file, and read this back in.
Ideally want to a plist like this
Root Dictionary
Issues Dictionary
Governance Number 1
Innovation and technology Number 0
etc
I've got as far as working this out
// Designate plist file
NSString *path = [[NSBundle mainBundle] pathForResource: #"issues" ofType:#"plist"];
// Load the file into a Dictionary
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
self.allNames= dict;
[dict release];
NSLog(#"Dict is %#", allNames); // All the data in the pList file
NSMutableArray *issueSection = [allNames objectForKey:#"Issues"];
NSLog(#"Issues is %#", issueSection); // The data is the Issues Section
NSString *issueVal = [issueSection objectForKey:#"Governance"];
NSLog(#"Governance is %#", issueVal); //The value of the Governance key
But what I really want to do is loop through the Issues Dictionary and get the key/value pairs so
key = cell.textLabel.text
value = UITableViewCellAccessoryCheckmark / UITableViewCellAccessoryNone
depending wether it's 1 or 0
I'm assuming that I can still assign one of the three NSMutableArrays to currentArray as I did in the hardcoded version, and use currentArray to reload the tableview.
Then amend this code to build the tableview
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [names objectForKey:key];
static NSString *CellIdentifier = #"Cell";
//UITableViewCell *cell = [self.prefsTableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
UITableViewCell *cell = [self.prefsTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell=[[[UITableViewCell alloc]
initWithFrame:CGRectZero
reuseIdentifier: CellIdentifier] autorelease];
}
cell.textLabel.text = [nameSection objectAtIndex:row];
return cell;
But my brain has melted, I've spent about six hours today reading up on pLists, NSArrays, NSMutableDisctionaries, standardUserDefaults to little avail.
I've managed to UITableViews inside UINavigationViews, use SegmentedControls, download asynchronous XML, but now I'm finally stuck, or fried, or both. Over what should be fairly simple key/value pairs.
Anyone care to give me some idiot pointers?
Typing it out led to another post with that one little word I needed to get me back on track :)
Use key/value pairs in a pList to stipulate the name of the cell and wether it was selected or not by the user.
plist is based on a structure like this
Root Dictionary
Services Dictionary
Peaches String 1
Pumpkin String 0
Here's how I grabbed three Dictionary arrays from a pList and used the key/value pairs to reload a tableview depending on which segmentControl was touched:
- (void)viewDidLoad {
[super viewDidLoad];
// Designate plist file
NSString *path = [[NSBundle mainBundle] pathForResource: #"issues" ofType:#"plist"];
// Load the file into a Dictionary
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
self.allNames= dict;
[dict release];
// Create the Named Dictionaries from Dictionary in pLIst
NSMutableDictionary *allIssues = [self.allNames objectForKey:#"Issues"];
self.prefsIssuesList = allIssues;
[allIssues release];
NSMutableDictionary *allIndustries = [self.allNames objectForKey:#"Industries"];
self.prefsIndustriesList = allIndustries;
[allIndustries release];
NSMutableDictionary *allServices = [self.allNames objectForKey:#"Services"];
self.prefsServicesList = allServices;
[allServices release];
// Assign the current Dictionary to out placeholder Dictionary
currentDict = [[NSMutableDictionary alloc] init];
currentDict = self.prefsIssuesList;
}
Then styling the table cells
- (UITableViewCell *)tableView:(UITableView *)prefsTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSArray *keysArray = [self.currentDict allKeys];
NSString *theKey = [keysArray objectAtIndex:row];
NSString *theValue = [self.currentDict objectForKey: [keysArray objectAtIndex:row]];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.prefsTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell=[[[UITableViewCell alloc]
initWithFrame:CGRectZero
reuseIdentifier: CellIdentifier] autorelease];
}
cell.textLabel.text = theKey;
if (theValue == #"0") {
cell.accessoryType = UITableViewCellAccessoryNone;
}else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
return cell;
}
The if clause at the end doesn't seem to be working, I'll post that as a new question (unless anyone comments quickly!)
Finally the segmentControls assign the different dictionaries to the placeholder array and reload the tableview
This took me a very long day to figure out (as a noobie) so I hope it helps someone
-(IBAction) segmentedControlIndexChanged{
switch (self.segmentedControl.selectedSegmentIndex) {
case 0:
//currentArray = self.prefsIssuesList;
currentDict = self.prefsIssuesList;
break;
case 1:
//currentArray = self.prefsIndustriesList;
currentDict = self.prefsIndustriesList;
break;
case 2:
//currentArray = self.prefsServicesList;
currentDict = self.prefsServicesList;
break;
default:
//currentArray = self.prefsIssuesList;
currentDict = self.prefsIssuesList;
break;
}
[prefsTableView reloadData];
}
Shout if there's a neater or better way of d

How does Fast Enumeration (looping) work in Objective-C? (ie: for (NSString *aString in aDictionary)...)

I'm working on implementing a customized searchBar for a fairly complex table and have come across this code pattern AGAIN. This is a sample from the Beginning iPhone Development book:
- (void)handleSearchForTerm:(NSString *)searchTerm
{
NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init];
[self resetSearch];
for (NSString *key in self.keys)
{
NSMutableArray *array = [self.names valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (NSString *name in array)
{
if ([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}
[self.keys removeObjectsInArray:sectionsToRemove];
[sectionsToRemove release];
[table reloadData];
}
The part I'm curious about is the "for (NSString *name in array)" section. What is this doing exactly? It seems to create a string for every item in the array. Also, how does this work with dictionaries?
Thanks!
This construct is a different kind of for loop that runs over items in an Objective-C collection, rather than a C array. The first part defines an object that is being set to one element in the collection each run of the loop, while the second part is the collection to enumerate. For example, the code:
NSArray *array = [NSArray arrayWithObjects:#"foo", #"bar", nil];
for(NSString *string in array) {
NSLog(string);
}
would print:
foo
bar
It's defining an NSString *string that, each run of the loop, gets set to the next object in the NSArray *array.
Similarly, you can use enumeration with instances of NSSet (where the order of objects aren't defined) and NSDictionary (where it will enumerate over keys stored in the dictionary - you can enumerate over the values by enumerating over keys, then calling valueForKey: on the dictionary using that key).
It's extremely similar to the construct in C:
int array[2] = { 0, 1 };
for(int i = 0; i < 2; i++) {
printf("%d\n", array[i]);
}
which prints:
0
1
It's just a syntactical way of making the code more readable and hiding some of the fancy enumeration that goes into listing objects in an NSArray, NSSet, or NSDictionary. More detail is given in the Fast Enumeration section of The Objective-C 2.0 Programming Language document.
This is called fast enumeration. It loops through the array, setting key to each item. It's the same, functionally, as doing this:
NSString *key;
for ( NSInteger i = 0; i < [[ self keys ] count ]; i++ ) {
key = [[ self keys ] objectAtIndex:i ];
NSMutableArray *array = [self.names valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (NSString *name in array)
{
if ([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}
It's a for loop with one iteration for each key in the dictionary.
The for..in construct is called Fast enumeration. You can read more about it in Objective-C 2.0 Programming Guide.
How it works with an object depends on it's implementation of the NSFastEnumeration protocol. The NSDictionary class reference describes how it works with dictionaries:
On Mac OS X v10.5 and later, NSDictionary supports the NSFastEnumeration protocol. You can use the for…in construct to enumerate the keys of a dictionary, as illustrated in the following example.