Hi I always seem to get exception when I use objectAtInded method to retrieve NSString from an array. I am reading data from a dictionary which is in the "PropertyList.plist" file.My code is
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"PropertyList"
ofType:#"plist"];
names = [[NSDictionary alloc]
initWithContentsOfFile:path];
keys = [[[names allKeys] sortedArrayUsingSelector:
#selector(compare:)] retain];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [names objectForKey:key];
static NSString *SectionsTableIdentifier = #"SectionsTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SectionsTableIdentifier] autorelease];
}
cell.textLabel.text = [nameSection objectAtIndex:row];
return cell;
}
The exception happens on the method "cellForRowAtIndexPath" in the line
cell.textLabel.text = [nameSection objectAtIndex:row];
The error message is
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary objectAtIndex:]: unrecognized selector sent to instance 0x6832440
The plist file is
Where ever I use "[nameSection objectAtIndex:row];" type of statement it always get exception.
The reason for this can be following
[names objectForKey:key];. This statement could give output a NSMutableDictionary type and you are taking a NSArray from that. OR
If it is an array then use the below code for getting the nameSection
NSMutableArray *nameSection = (NSMutableArray*)[names objectForKey:key];
// using (NSMutableArray*) before the code is for external typecasting to tell the compiler that the output is of NSMutableArray type.
Hope this helps.
EDIT:-
use the method below to get your dictionary from the plist file
-(NSMutableDictionary *) GetDictDataFromPlistFile:(NSString *) fileName
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //1
NSString *documentsDirectory = [paths objectAtIndex:0]; //2
NSString *path = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.plist",fileName]]; //3
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path]) //4
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:fileName ofType:#"plist"]; //5
[fileManager copyItemAtPath:bundle toPath: path error:&error]; //6
}
NSMutableDictionary *dictData = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
return dictData;
}
Here, name contains only one key of root.
What you need for name is the value of key Root.
Please retry!
Like #scorpiozj said 'names' contains only key of 'Root'. So what I did I know is not a very good way to do it. I am sure there is some other way. I changed the 'viewDidLoad' method to this,
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"PropertyList"
ofType:#"plist"];
names = [[NSDictionary alloc]
initWithContentsOfFile:path];
keys = [names allKeys];
NSString *key = [keys objectAtIndex:0];
names = [names objectForKey:key];
keys = [[[names allKeys] sortedArrayUsingSelector:#selector(compare:)] retain];
NSLog(#"keys = %# names = %#",keys,names);
}
It works! Any idea how to do it better will be appreciated though.
Related
So I have a download manager in my app. I have taken it upon myself to liven it up today.
I have implemented UITableViewCellStyleSubtitle, and is displaying properly.
I want to add more than 1 line to it. Right now I'm stuck in choosing either the file size or the formatted date.
How would I do both? i.e.
Cell Title
Date: (followed by file size) or
File Size:
Below is the relevant code I'm working with.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [(UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier] autorelease];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
[cell setSelectionStyle:UITableViewCellSelectionStyleBlue];
}
// Configure the cell.
cell.textLabel.text = [directoryContents objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryNone;
[cell.layer setBorderColor:[UIColor colorWithRed:30/255.0 green:30/255.0 blue:30/255.0 alpha:1.0].CGColor];
[cell.layer setBorderWidth:1.5f];
cell.textLabel.numberOfLines = 0;
//Get file size
NSError *error;
NSString *fileName = [directoryContents objectAtIndex:indexPath.row];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"my folder"];
path = [path stringByAppendingPathComponent:fileName];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] intValue];
//Setting the date
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"my folder"];
filePath = [filePath stringByAppendingPathComponent:fileName];
NSDate *creationDate = nil;
NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
creationDate = attributes[NSFileCreationDate];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MM-dd-yyyy"];
NSString *dateString = [dateFormatter stringFromDate:creationDate];
/////This is where I need to blend the dateString with the file size//////
cell.detailTextLabel.text = [NSString stringWithFormat:dateString, #"%#", [self formattedFileSize:fileSize]];
cell.detailTextLabel.numberOfLines = 2;
return cell;
}
Thank you in advanced.
I tried this out, and for some reason, setting numberOfLines to 2 didn't work for me either, but setting it to anything greater then 2, or setting it to 0 worked.
You need to format your two strings properly. This is not correct syntax,
[NSString stringWithFormat:dateString, #"%#", [self formattedFileSize:fileSize]]
It should be like this,
[NSString stringWithFormat:#"%#\n%#", dateString, [self formattedFileSize:fileSize]];
Figured this out. Just had to tweak around with the format of the string.
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#\n%#", dateString, [self formattedFileSize:fileSize]];
As posting this answer the page refreshed and seen that rdelmar is also correct in his updated post showing the proper syntax.
Credit and thanks to him for the help in helping me think this out.
I'm a newbie to Objective-C so any help is greatly appreciated.
I have a TableView that is successfully pulling back the list from my Data.plist file within my Xcode project but I need it to pull from the Documents Directory.
I have seen many posts about this but can't seem to get it to work for me.
Here is my .m file below. Like I said, it pulls back the data but I need it to be dynamically changed based on my Documents plist copy.
Thanks in advance!
#synthesize content = _content;
- (NSArray *)content
{
if (!_content) {
_content = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"]];
}
return _content;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.content count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = [[self.content objectAtIndex:indexPath.row] valueForKey:#"Name"];
cell.detailTextLabel.text = [[self.content objectAtIndex:indexPath.row] valueForKey:#"Score"];
return cell;
}
Try this:
Step 1: In .h File declare a global variable
#property (nonatomic,retain) NSMutableDictionary *contents;
Step 2: In .m file set synthesis.
#synthesize contents;
Step 3: Move plist file from mainbudle into documents directory
-(void) createPlistDocuments
{
// Get path to documents directory
NSArray *arrayPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// Finds the contained Documents directory
NSString *documentsDirectory = [arrayPaths objectAtIndex:0];
NSError *error;
// Create an object that we will later use to look for a file and return a boolean value on whether or not it exists
NSFileManager *manager = [NSFileManager defaultManager];
// File we want to move, stored in original top level directory
NSString *demoFile = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
// Define where we want it moved to and name it
NSString *demoFileMoved = [NSString stringWithFormat:#"%#/data.plist", documentsDirectory];
// Attempt the copy
if ([manager copyItemAtPath:demoFile toPath:demoFileMoved error:&error] != YES)
NSLog(#"Unable to move file: %#", [error localizedDescription]);
}
Step 4: Read plist data from document directory
- (void)viewDidLoad
{
[super viewDidLoad];
[self createPlistDocuments];
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
NSString *path = [NSString stringWithFormat:#"%#/data.plist",documentsDirectoryPath];
contents = [NSArray arrayWithContentsOfFile:path];
NSLog(#"%d", contents.count);
}
For brief explanation follow this sample... TechDevMobile(IOS-Message-Chat)
I have some code that displays file name in my uitableview. however once deleting a file and refreshing i receive an error. Here is my code to display my file name, my delete button actions and the error:
Firstly when a button is pressed i run this code which works when adding a file to the table:
-(IBAction)refresh{
[[self mytable] reloadData];
}
Secondly I have this code to get and display the values the table is going to display. This works fine until the deletion and update occurs:
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
filePathsArray = [[[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil]mutableCopy];
mytable.dataSource = self;
mytable.delegate = self;
}
-
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(!filePathsArray)
{
return 0;
}
if ([filePathsArray count] > 0)
return [filePathsArray count];
else
return 0;
}
-
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
//add code here for when you hit delete
NSString *currentFileName = filePathsArray[indexPath.row];
NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
NSString *filePath = [documentsDirectoryPath stringByAppendingPathComponent:currentFileName];
fileURL = [NSURL fileURLWithPath:filePath];
NSLog(#"urlstring %#",fileURL);
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:filePath error:NULL];
NSLog(#"successfully deleted");
}
}
-
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MainCell"];
}
NSLog(#"urlstring %#",[filePathsArray objectAtIndex:indexPath.row]);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil];
cell.textLabel.text = [filePathsArray[indexPath.row] lastPathComponent];
filePathsArray = [[NSArray alloc] initWithArray: [[[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil]mutableCopy]];
return cell;
}
-
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
The error i receive is this:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 6 beyond bounds [0 .. 5]'
*** First throw call stack:
(0x2049012 0x1473e7e 0x1ffeb44 0x3c3a 0x69b8fb 0x69b9cf 0x6841bb 0x694b4b 0x6312dd 0x14876b0 0x2c84fc0 0x2c7933c 0x2c79150 0x2bf70bc 0x2bf8227 0x2bf88e2 0x2011afe 0x2011a3d 0x1fef7c2 0x1feef44 0x1feee1b 0x23dc7e3 0x23dc668 0x5e0ffc 0x2212 0x2145)
libc++abi.dylib: terminate called throwing an exception
(lldb)
I receive this error due to:
NSLog(#"urlstring %#",[filePathsArray objectAtIndex:indexPath.row]);
but once removed it says the problem is with the next line. Can anybody help?
Because of the way you're setting data in filePathsArray, your tableView is rendering one extra cell more than needed.
After you delete a file, update your filePathsArray.
What happens is that in your commitEditingStyle method you delete a file, but you do not update the filePathsArray object to remove the subpath for that file.
The only place where you update filePathsArray is in cellForRowAtIndexPath which is called after numberOfRowsInSection
So basically:
E.g. your filePathsArray contains subpaths for 6 files.
You delete a file in commitEditingStyle but you don't update filePathsArray (which still contains 6 subpaths).
You press the button to reloadData
numberOfRowsInSection is called to get the number of cells to be displayed on your UITableView... This returns [filePathsArray count] which is still 6.
cellForRowAtIndexPath is now called for every row.
Inside the above method, you call filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil]; so now your filePathsArray has 5 objects because of the deleted file.
But guess what.... Your UITableView has already started rendering 6 rows for the old filePathsArray count.
Now when it renders cell number 6 (indexPath.row = 5), it calls cell.textLabel.text = [filePathsArray[indexPath.row] lastPathComponent]; and the last index of your array is now 4. This causes the crash.
Simply do filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil]; in your commitEditingStyle and the error should stop.
Also, try not modifying your data source object in cellForRowAtIndexPath. Take the filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil]; call out of there as your array is now being updated in commitEditingStyle.
In tableView:cellForRowAtIndexPath:, remove this code: filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil];
In tableView:commitEditingStyle:,add this code:
[filePathsArray removeObjectAtIndex:indexPath.row];
This should maintain the integrity of the data you're using to populate the table view (so you don't change the number of items when the table doesn't know about it and you do change it when the table thinks you have).
I have PropertyList.plist file in the "Supporting Files" folder. I have made a dictionary in it. The plist file is:
MY ViewController.m file code is
#implementation GroupedInexedViewController
{
NSDictionary *names;
NSArray *keys;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"PropertyList"
ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc]
initWithContentsOfFile:path];
names = dict;
[dict release];
NSArray *array = [[names allKeys] sortedArrayUsingSelector:
#selector(compare:)];
keys = array;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [keys count];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [names objectForKey:key];
return [nameSection count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [names objectForKey:key];
static NSString *SectionsTableIdentifier = #"SectionsTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SectionsTableIdentifier] autorelease];
}
cell.textLabel.text = [nameSection objectAtIndex:row];
return cell;
}
-(NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *key = [keys objectAtIndex:section];
return key;
}
Unfortunately the "keys" array doesnt contain any element. Because I made an alert with its count value which is "keys.count" and it was zero. and also the "names" count was also zero. The path variable on vieDidLoad method shows the correct path. but it cant read from the dictionary of the plist file's.
Edit: I used nslog and it shows that in "viewDidLoad" method "names" is able to load the dictionary . but "keys" array is unable to load it.
What #EugeneK said worked but got sigabrt in "cellForRowAtIndexPath" method on the line
cell.textLabel.text = [nameSection objectAtIndex:row];
the error message is
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary objectAtIndex:]: unrecognized selector sent to instance 0x6832440".
The problem was nameSection variable was appearing as a NSDictionary type object which doesn't support objectAtIndex method.
So what I did I know is not a very good way to do it. I am sure there is some other way. I changed the 'viewDidLoad' method to this,
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"PropertyList"
ofType:#"plist"];
names = [[NSDictionary alloc]
initWithContentsOfFile:path];
keys = [names allKeys];
NSString *key = [keys objectAtIndex:0];
names = [names objectForKey:key];
keys = [[[names allKeys] sortedArrayUsingSelector:#selector(compare:)] retain];
NSLog(#"keys = %# names = %#",keys,names);
}
It works! Any idea how to do it better will be appreciated though.
Martin R is right. Please see comments in your code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"PropertyList"
ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc]
initWithContentsOfFile:path]; //retainCount of dict is 1
names = dict; // you made weak reference to dict
[dict release]; // retainCount is 0 - dict is being dealloced
NSArray *array = [[names allKeys] sortedArrayUsingSelector:
#selector(compare:)]; // you try to get data from dealloced object
keys = array;
}
Try to do the following:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"PropertyList"
ofType:#"plist"];
names = [[NSDictionary alloc]
initWithContentsOfFile:path];
keys = [[[names allKeys] sortedArrayUsingSelector:
#selector(compare:)] retain];
}
And do not forgot to release names and keys in your view controller dealloc.
Actually this is to follow up my question before
UITableView show only first row.
Now my problem is that I only want to view the 10 list in my plist. If there are 11 items, the first item will be replace by the second and so on so my list only 10 items.
And this is my code to save to the plist:
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"Data.plist"];
NSMutableArray *array = [NSMutableArray arrayWithContentsOfFile:plistPath];
if (nil == array) {
array = [[NSMutableArray alloc] init];
}
NSMutableArray *list = [[NSMutableArray alloc]init];
[list addObject:resi.text];
[array addObject:list];
[array writeToFile:plistPath atomically: TRUE];
And this is the whole code for my table view that have been modified
- (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 [array count];
}
- (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];
}
NSArray *list = (NSArray *)[self.array objectAtIndex:indexPath.row];
if(list && [list count] > 0) { //to check and avoid any crash
cell.textLabel.text = [list objectAtIndex:0];
}
// Configure the cell...
return cell;
}
- (void)viewWillAppear:(BOOL)animated
{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"Data.plist"];
array = [NSMutableArray arrayWithContentsOfFile:plistPath];
[myHistoryTable reloadData];
}
if ([array count] > 10) {
array = [array subarrayWithRange:NSMakeRange([array count] - 10, 10)];
}
if you don't want to overwrite the original array create a second one which serves as the dataSource of the tableView:
array = /* load from plist */;
if ([array count] > 10) {
self.dataSourceArray = [array subarrayWithRange:NSMakeRange([array count] - 10, 10)];
}
else {
self.dataSourceArray = array;
}