I have an app that allows users to save and open text files. Saving and opening files is fairly straightforward and easy, but I have to give the user an easy way to select a file to open. I decided to do this with a UITableView.
When the TableView loads in my view controller, my goal is to populate the TableView with the names of all of the user's text files from within the Documents folder (part of the iOS App Sandbox).
I have a general idea of how to do this:
Get the contents of the Documents folder and place it in an array
NSString *pathString = [[NSBundle mainBundle] pathForResource:#"Documents" ofType:#"txt"];
NSArray *fileList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:pathString error:nil];
NSLog(#"Contents of directory: %#", fileList);
But this always returns: (null) in the output window. What is the path for my app's document folder?
Place the array in the UITableView
I figured I'd use the numberOfRowsInSection method to do this. Is this the right place for performing this type of operation? Of should I use a different method?
Finally, the app will get the value of the selected cell and use that value to open the file.
My main question here is: How can I place items (particularly the contents of directory) into an NSArray and then display that array in a UITableView?
Any help is much appreciated!
You can get the path using:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
You can get the contents of the directory using:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *fileList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
As for displaying an NSArray in a UITableView, you should review Apple's documentation on UITableView Data Source protocol:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html
You use the cellForRowAtIndexPath method to actually populate the table, something like this would work:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyIdentifier"] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [fileList objectAtIndex:indexPath.row]
return cell;
}
Related
I use a PLIST to populate a TableView in my app. I want the TableView to be in order of how items were added, due to needing a good way to edit it later on, so the indexPath.row will match up with the indexPath of the PLIST. I get the TableView populated by going from the PLIST to NSDictionary to NSArray. The issue is that the TableView keeps listing everything in alphabetical order and not the order it was listed
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:self.selectedCountry];
_plist = [NSMutableDictionary dictionaryWithContentsOfFile: plistPath];
_content = [_plist allKeys];
I know this is a common question on Stack Overflow, but unfortunately none have been able to lead me to my solution.
I'm trying to list the files in my documents directory in a UITableView. (I also have another tableview displaying devices I'm connected to)
I know I'm getting this error because its saying that my array has 0 objects, and I'm trying to access the object at index 1.
But I have a file in my documents directory.
If I have 2 files, when I delete 1, I get this error. When the app is relaunched, it shows the UITableView correctly with the file deleted.
I got my code from here:
Objective-C: How to list files from documents directory into a UITableView?
this is my content:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (tableView == _tblConnectedDevices){
return [_arrConnectedDevices count];
}
if ([filePathsArray count] > 0){
return [filePathsArray count];
}
else
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if (tableView == _tblConnectedDevices){
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellIdentifier"];
}
cell.textLabel.text = [_arrConnectedDevices objectAtIndex:indexPath.row];
return cell;
} else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil];
NSString *last = [documentsDirectory stringByAppendingPathComponent:[filePathsArray objectAtIndex:indexPath.row]];
NSString *last2 = [[last lastPathComponent] stringByDeletingPathExtension];
cell.textLabel.text = last2;
return cell;
}
}
My delete void:
-(IBAction)deleteFile:(id)sender{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *fullPath = [[paths lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.txt", fileName]];
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
[_cellView reloadData];
}
Reload your filePathArray in deleteFile: Also set breakpoint and look how many files you have in array
You should refactor your code. However:
I assume that you get the error in this line:
NSString *last = [documentsDirectory stringByAppendingPathComponent:[filePathsArray objectAtIndex:indexPath.row]];
What's going wrong:
A. You have an array with the paths in-memory. When the table view asks you for the number of items, you return the count of that array.
B. When the table view asks for an item in a row, you read the directory. (Well, that's not that fast the method should be.)
filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:documentsDirectory error:nil];
Then you look for the file with the index of indexPath.row:
NSString *last = [documentsDirectory stringByAppendingPathComponent:[filePathsArray objectAtIndex:indexPath.row]];
This "works" (it is akin of working) as long as you have at least as many files in the directory as in your array.
When you remove the file from the directory, it is still an item in the array. So you say to the table view that there are as many rows as items in the array. When the table view tries to get it, you look to the disk and find less files (the deleted is missed). So the array you get from disk has one item less than the items in-memory.
To fix it: Update your in-memory array. Or make a decision: Work in-memory or work on disk.
i am unable to delete a specific data from my plist file. I googled so many answers in stackoverflow, but i am unable to solve my problem.
Here is my code:
In ViewDidLoad i retrieve data from plist file.
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"manuallyData.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
plistPath = [[NSBundle mainBundle] pathForResource:#"manuallyData" ofType:#"plist"];
}
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.nameArr = [dict objectForKey:#"Name"]; // Here i got all my name
self.countryArr = [dict objectForKey:#"Country"]; // here i got all my country
}
My deleted code :
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"manuallyData.plist"];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:(NSString *)plistPath];
NSString *key = [self.nameArr objectAtIndex:indexPath.row];
[dictionary removeObjectForKey:[NSString stringWithFormat:#"%#", key]];
[dictionary writeToFile:plistPath atomically:YES];
self.nameArr = [dictionary allKeys];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
When i build this App i got this error :
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Please anybody suggest me, what i have to do ?
Thanks in Advanced. :)
Like Joe Blow, I think the problem is in how you're managing your table view.
Try replacing [tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic] with [tableView reloadData] and see what happens.
The reason the file is not being updated is probably because the exception is thrown before the file is written.
Now onto your problem, check your implementation of numberOfRowsInSection:. That's probably where the bug is.
Also, check the return of [dictionary writeToFile:plistPath atomically:YES]. You shouldn't assume the file is written.
if ([dictionary writeToFile:plistPath atomically:YES]) {
self.nameArr = [dictionary allKeys];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
} else {
NSLog(#"Failed saving %#", plistPath);
}
Your root cause lies in these two lines i guess. Not damn sure
self.nameArr = [dict objectForKey:#"Name"]; // Here i got all my name
self.nameArr = [dictionary allKeys]; //I can see your dictionary contains only two keys, Name and Country
I will expalin little bit. In first you are getting your names using the key "Name". Later you are getting the same name array like self.nameArr = [dictionary allKeys];. You got the difference? in 2nd case your array contain the keys [Name, Country]. I just explained the code which is causing error.
Then the code you have written for deletion also not proper. You have get the name and country array again like as you did initially. Then delete from there, write it back. Reading and writing file each time is not good. It will affect the app performance.
So I've been looking for a solution to this issue, but can't seem to find anything. I have an array that I load with image paths using the below method
- (IBAction)btnPictures:(id)sender {
// Create the next view controller.
ImageGalleryViewController *galleryViewController = [[ImageGalleryViewController alloc] initWithNibName:#"ImageGalleryViewController" bundle:nil];
// Search for paths and get users home directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
// get path at first index (in case multiple returned
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
NSArray *files = [dirContents filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"pathExtension IN %#", #"png"]];
// Pass the selected object to the new view controller.
[galleryViewController setImageArray:files];
// Push the view controller.
[self.navigationController pushViewController:galleryViewController animated:YES];
}
That image array is then sent to the below method in my UICollectionViewController
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ImageGalleryViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"ImageGalleryViewCell" forIndexPath:indexPath];
NSLog(#"Image at index %#", [imageArray objectAtIndex:indexPath.row]);
[cell.galleryImage setImage:[UIImage imageWithContentsOfFile:[imageArray objectAtIndex:indexPath.row]]];
[cell setBackgroundColor:[UIColor whiteColor]];
return cell;
}
I verified the objectAtIndex is returning a valid image name through my NSLog, but for some reason the cell is not displaying it.
Any help is greatly appreciated, thanks in advance.
The array returned by -contentsOfDirectoryAtPath: of NSFileManager doesn't provide the full file path but just the name of files and directories. You have to append each values in the array to the path to your Document Directory.
Something like this:
NSString *documentsDirectoryPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSString *imagePath = [documentsDirectoryPath stringByAppendingPathComponent:[imageArray objectAtIndex:indexPath.row]];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
I suspect, you are not initialising any cells before using (dequequing) one. Initialise your cell, and try again. Good Luck!
PS: by initialising I mean, alloc init your cell.
I have written an app that allows the user to take photos which are stored in an NSArray and which are then saved in the Documents directory. When they are reloaded the pictures are stored in an UITableView - note only one photo is currently loaded due to problem 1.
I have two problems:
1 - This works but only for one photo as I need to be able work out how many photos have been stored.
2 - When I reload the data and put into an UIImageView the rotation of the photos is incorrect. Landscape photos are upside down and portrait photos are landscape.
Any help with either of these issues greatly appreciated.
Saving the photos
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
for (int i=0;i<photoCount;i++)
{
UIImage *image = [photos objectAtIndex:i];
NSString *savePicturePath = [saveFolderPath stringByAppendingPathComponent:[NSString stringWithFormat:#"image_%d",i]];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savePicturePath atomically:NO];
}
Retrieving the photos
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *getPicPath = [documentsDirectory stringByAppendingPathComponent:#"image_0"];
NSData *picData = [[NSFileManager defaultManager] contentsAtPath:getPicPath];
UIImage *loadedImage = [UIImage imageWithData:picData];
[photos addObject:loadedImage];
[self.tableView reloadData];
Adding image to table
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"photoCell"];
UIImage *currentImage = [self.photos objectAtIndex:indexPath.row];
cell.imageView.image = currentImage;
return cell;
}
You could solve the first issue by creating your own directory and storing all images to that directory. Then when you read use NSFileManager's contentsOfDirectoryAtPath to get an array of all the images in that directory. Or you could read the content of the documents folder and use NSPredicate to filter out only images by file extension (using filteredArrayUsingPredicate).
As for the rotation issue, the answer is available here.
The rotation issue ... simple:
Change
NSData *imageData = UIImagePNGRepresentation(image);
to
NSData *imageData = UIImageJPGRepresentation(image, 1.0);
And everything is perfect. Thanks to Joel for pointing me in the right direction.