RootViewController - tableView:cellForRowAtIndexPath - Load image from assetsLibrary - iphone

StackOverflow friends and colleague programmers,
My RootViewController (a flowerTableView on a view) should display cell's with title, subtitle and an image thumbnail (loaded from the camera roll). A very basic table I guess.
All content is stored in 'Core Data' but the images are stored as imagePaths to the camera roll. Example: flower.imagePath = assets-library://asset/asset.JPG?id=1000000002&ext
Using the code at the bottom everything should run smoothly but it doesn't . When starting the App the title's and subtitle's are shown, but the images are not. When I press a cell, which display's the detail view, and pop back again to the main view the image for this specific cell is shown.
When I press 'Show All', a button on a toolbar which executes the following code
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
[fetchRequest setPredicate:nil];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self.flowerTableView reloadData];
and reloads the table, all the beautiful flowers are now shown.
Why didn't the flowers be displayed the first time? Is this a caching problem?
When debugging this code the string: 'This debug string was logged after this function was done' was logged after loading the table on starting the app, not after pressing 'Show All'
This means that all images are loaded from the camera roll successfully but attached to the cell after the cell was displayed and therefore not shown.
The same line is printed as intended when pressing 'Show All'
Hope someone can tell me what's going on here and even better, what to change in my code to make this work. I'm stuck at the moment...
Thank for helping me out!
Edwin
:::THE CODE:::
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
Flower *flower = [fetchedResultsController_ objectAtIndexPath:indexPath];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
// Display the image if one is defined
if (flower.imagePath && ![flower.imagePath isEqualToString:#""])
{
// Should hold image after executing the resultBlock
__block UIImage *image = nil;
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset *asset)
{
NSLog(#"This debug string was logged after this function was done");
image = [[UIImage imageWithCGImage:[asset thumbnail]] retain];
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error)
{
NSLog(#"Unresolved error: %#, %#", error, [error localizedDescription]);
};
[assetsLibrary_ assetForURL:[NSURL URLWithString:flower.imagePath]
resultBlock:resultBlock
failureBlock:failureBlock];
[cell.imageView setImage:image];
}
return cell;
}

-[ALAssetsLibrary assetForURL:resultBlock:failureBlock] runs asynchronously. That means that the call returns immediately in your tableView:cellForRowAtIndexPath: method. The cell is displayed in the table view before the actual loading of the asset has taken place.
What you need to do is set the image for the cell in the result block. Something like this:
if (flower.imagePath && ![flower.imagePath isEqualToString:#""])
{
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset *asset)
{
NSLog(#"This debug string was logged after this function was done");
[cell.imageView setImage:[UIImage imageWithCGImage:[asset thumbnail]]];
//this line is needed to display the image when it is loaded asynchronously, otherwise image will not be shown as stated in comments
[cell setNeedsLayout];
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error)
{
NSLog(#"Unresolved error: %#, %#", error, [error localizedDescription]);
};
[assetsLibrary_ assetForURL:[NSURL URLWithString:flower.imagePath]
resultBlock:resultBlock
failureBlock:failureBlock];
}

Fellow Overflowist let me propose the following solution:
rather than creating your cells in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
create an NSMutableArray populated with UITableViewCells (the same ones you create in the cellForRowAtIndexPath method) that you want to display in the table
and simply return the relevant UITableViewCell (which will be an object in the NSMutableArray) in the cellForRowAtIndexPath method.
This way the cellForRowAtIndexPath method will show cells which have already been loaded and are ready to be shown.

Related

New objects not saving properly when added to tableView

I'm having some issues getting Core Data to save new rows that I add when using a UITextField. Here is my method for inserting objects into my table view. What should happen is when I click the add button, a textfield should be added, and then go right into edit mode. Then when the user clicks done on the keyboard the textfield should end editing and then the textfield should save the entry into core data.
Edit: removed call to textFieldDidEndEditing in the insertNewObject:(id)sender method. It was crashing the app
- (void)insertNewObject:(id)sender {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
TehdaItem *item = [NSEntityDescription insertNewObjectForEntityForName:#"TehdaItem" inManagedObjectContext:context];
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
// Putting the cell in edit mode
TehdaTableViewCell *editcell;
for (TehdaTableViewCell *cell in [self.tableView visibleCells]) {
if (cell.itemLabel.text == item.itemTitle) {
editcell = cell;
break;
}
}
[editcell.itemLabel becomeFirstResponder];
// The cell needs to call the method textfield did end editing so that it can save the new object into the store
// Save the context.
NSError *error = nil;
if (![context 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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Here is my textFieldDidEndEditing method:
- (void)textFieldDidEndEditing:(UITextField *)textField {
TehdaTableViewCell *cell = (TehdaTableViewCell *) textField.superview.superview;
TehdaItem *item = [self.fetchedResultsController objectAtIndexPath:[self.tableView indexPathForCell:cell]];
//TehdaTableViewCell *cell;
item.itemTitle = cell.itemLabel.text;
}
Not really sure where to go from here. Any help would be appreciated.
Thanks.
You can use
NSError *error;
[item.managedObjectContext save:&error];
if (error) {
// Triage the problem and respond appropriately
}
in the - (void)textFieldDidEndEditing:(UITextField *)textField method. But if I were you I'd do some validation before you save the object.

uitableview should load after retreving data from webservice with huge data

I have a requirement that my UITableview should load data after retrieving data from web service. When retrieving huge data, my uitableview is displaying empty. Instead of that, a UIActivityIndicator should appear until data is loaded into the UITableview.
I am trying as below, but not able to get the desired functionality. Please help.
Also let me suggest what is the best approach to load data into UITableview, when data returned from webservice is huge.
Thanks in advance.
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
UILabel *lblName = (UILabel *)[cell viewWithTag:1];
[lblName setText:[myArray objectAtIndex:[indexPath row]]];
return cell;
}
- (void)dataLoading
{
myActivityIndicator=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[myActivityIndicator setFrame:CGRectMake(140, 170, 40, 40)];
[self.view addSubview:myActivityIndicator];
[myActivityIndicator startAnimating];
NSString *playersDatalink = [NSString stringWithFormat:#"Webservice link"];
NSURL *JsonPlayersDataURL = [NSURL URLWithString:playersDatalink];
NSString *JsonPlayersData = [[NSString alloc] initWithContentsOfURL:JsonPlayersDataURL];
SBJSON *playersDataParser = [[SBJSON alloc] init];
NSDictionary *PlayersDicdata = (NSDictionary *) [playersDataParser objectWithString:JsonPlayersData error:nil];
NSDictionary *playersdata = (NSDictionary *) [PlayersDicdata objectForKey:#"players"];
NSLog(#"palyersdata is =%#",playersdata);
self.myArray = [NSMutableArray arrayWithCapacity:[playersdata count]];
for (NSDictionary *details in playersdata) {
[self.myArray addObject:[details objectForKey:#"teamid"]];
}
if ([playersdata count]==0) {
UIAlertView *myalert = [[UIAlertView alloc]initWithTitle:#"Webserive error " message:#"No data returned from webservice" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[myalert show];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(dataLoading) withObject:nil afterDelay:0.0];
[myActivityIndicator stopAnimating];
}
hi Download This zip http://www.sendspace.com/file/l48jxg Unzipe this file you getting two .h and .m for loading Indicatore view,. drag and drop this into Your Project.
Now you can use this like Bellow:-
if you wish to show Activity Indicator till you data not Load:-
import LoadingView.h into your Class,
create Object
loadingView =[LoadingView loadingViewInView:self.view:#"Please Wait.."];
at this time put this code of like olso yourTableView.userInterfaceEnable=FALSE;
and at the data Load finish method put this
[loadingView removeFromSuperview];
yourTableView.userInterfaceEnable=TRUE;
you getting loading View like:-
UPDATE
It doesn't metter your TableView delegate executing first.
1) If you Using NSURLConnection For Parsiong WebServices you can Reload Yout TableView Data after loading Finish at:-
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//your code
[yourTableView reloadData];
}
2) if you using NSXMLParser For Parsiong WebServices you can Reload Yout TableView Data after loading Finish at:-
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
//your code
[yourTableView reloadData];
}
Try this Activity Indicator which stops the user interaction until your data is loaded.
use MBProgressHud available on github
Have a Happy Coding..

Objective-C/iOS : Message sent to deallocated instance

This is my first post on stackoverflow.com so please be kind (rewind) ;)
I have a navigation based application whose purpose is to display blog posts (title) in a Table View (with JSON).
The problem I ran into occurred when a cell got out of the screen and then back in.
I was getting a EXC_BAD_ACCESS (because I sent a message to a deallocated instance), so I struggled to understand where it came from and I finally found a solution. But the fact is I don't exactly understand how the problem occurs. That's why I need someone to enlighten me, I think this is fundamental understanding !
When the connection to the JSON web service has finished loading, I parse the JSON code to obtain a list of blog posts (recentPosts), then I create a BlogArticle object for each post (blogArticle), store it in a MutableArray iVar (allEntries) and insert a row in the Table View :
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSError *error;
SBJsonParser *json = [[SBJsonParser new] autorelease];
NSDictionary *recentPostsData = [json objectWithString:responseString error:&error];
[responseString release];
NSArray *recentPosts = [recentPostsData objectForKey:#"posts"];
int i = 0;
for (NSDictionary *post in recentPosts) {
BlogArticle *blogArticle = [[BlogArticle alloc] initWithDictionary:post];
[allEntries insertObject:blogArticle atIndex:i];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationRight];
i++;
}
}
Here's the initialisation of the BlogArticle object which turned to be the origin of the problem :
- (id)initWithDictionary:(NSDictionary *)article
{
if (self = [super init])
{
// title = [[[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML] copy];
// title = [[NSString alloc] initWithString:[[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML]];
title = [[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML];
}
return self;
}
So every Objective-C programmer who isn't as noobish as me is able to tell that title is never allocated before being assigned. If I uncomment one of the two lines above it will work. The program crashes exactly when I try to initialize a cell with that title variable, here :
- (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];
}
NSLog(#"indexPath.row = %i", indexPath.row);
// Configure the cell.
BlogArticle *article = [allEntries objectAtIndex:indexPath.row];
cell.textLabel.text = article.title;
return cell;
}
Now, what I need to understand is why it does compile/work without allocating the iVar and where exactly it causes trouble (or where exactly the content of title is released causing the program to crash).
Any good resource (noob friendly) about memory management in iOS environment would be much appreciated.
Thanks in advance :)
This line
title = [[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML];
is allocating an autoreleased string. Essentially, think of it that an autoreleased string will get released at the end of the method (though it can last longer, it's useful to think of it that way).
You know the string is autoreleased because the name of the method gtm_stringByUnescapingFromHTML does not start with alloc, new, copy or mutableCopy.
You can add retain to this to stop it getting autoreleased:
title = [[[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML] retain];
Now you own the string, and it will not get released until you say so.
The best summary I know of is Apple's own documentation here.
Well, the problem is, that you have to initialize your object, if you want to manage the memory of it on your own. Why should you manage now the memory of title?
Quite simple:
Every object reference, that is stored in an Array, Set, Dictionary etc. is managed by the Array, Dictionary and Set.
If you now just use this reference (by writing: "title = ...") in your cell, you will add the reference also to the cell. And now the cell is also responsible for the object-reference. So if the tableView wants to release your cells, which will happen from time to time to save memory, the cell will release your title-object. And this would cause the NSDitionary to be quite sad, since the NSDictionary wants to take care about the objects stored within itself.
So you could write the following in the tableView-method:
cell.textLabel.text = [article.title retain];
Or the commented lines of your own method.
That means, you will "raise" the storage-level of your object up and if it gets released, the storage level itself will be decreased by one.
If the storage-level will reach zero, it will be completely released (that should happen, if your tablecell is released AND your NSDIctionary)
I hope i could help you a bit :)

Objective C threads, setting image in iOS tableViewCell

I've just started using threads and I'm trying to get better performance from my TableView in my app. Each tableViewCell has an imageView and the image is loaded from disk when the tableViewCell is created. I want to load the Image on a differant thread, then set the UIImageView on the main thread. My question is, can a method that is being ran on another thread return a value to the main thread? Is there a better approach for doin this?
Thanks for any help in advance.
maybe something like this, assuming your icons are in the document's directory:
#define DOCUMENTS_DIRECTORY [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
//inside - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:indexPath, #"indexPath", #"image1.png", #"imageName", nil];
[NSThread detachNewThreadSelector:#selector(loadIcon:) toTarget:self withObject:d];
//
- (void)iconLoaded:(NSDictionary*)dict {
[icons replaceObjectAtIndex:[[dict objectForKey:#"index"] intValue] withObject:[UIImage imageWithData:[dict objectForKey:#"imageData"]]];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[dict objectForKey:#"indexPath"]]];
}
- (void)loadIcon:(NSDictionary*)dict {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *filePath = [DOCUMENTS_DIRECTORY stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.jpg", [dict objectForKey:#"imageName"]]];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
[self performSelectorOnMainThread:#selector(iconLoaded:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:[dict objectForKey:#"indexPath"], "indexPath", imageData, #"imageData", nil] waitUntilDone:YES];
[pool drain];
}
you will need to keep track of which cells you are loading an image for, so you dont try to load one while it is already loading it. there may be some small syntax errors as i did not compile this, just wrote it freehand.
icons is an NSMutableArray holding a UIImage for each cell
Try this:
https://github.com/foursquare/fully-loaded
Yes, you can pass data to the main thread. See the following method in NSObject API docs:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
You get to pass a single object reference in the arg parameter.

Core Data app crashing with "controllerWillChangeContent : unrecognized selector sent to instance"

I've got a core data app with 2 views. The first view lists "Rooms", the second lists "Scenes" in rooms. The Rooms page has an edit NavItem button, which when pressed enables an add NavItem button. You can delete and add rooms from here. Added rooms simply appear with a default "New Room" name in the table. The second view is a list of Scenes in the selected room. Same deal here, you can delete and add Scenes, and added Scenes simply appear in the table with the name "New Scene". Nothing special really.
I'm using a FetchedResultsController in both view controllers, with the Scenes one having an NSPredicate to return only scenes from the selected room. I'm also using the controllerWillChangeContent, controllerDidChangeContent etc. delegate methods for the table view updates.
This all works fine at first, but usually after navigating around rooms and scenes then trying to delete a scene it will crash. It will inevitably crash if you play around with it long enough. It only happens when deleting a scene. If you press the edit button and delete a scene and it works, then all the following deletes in that edit session will always work. It will only ever crash on the first delete of the edit session.
The error I get is a strange one:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFType
controllerWillChangeContent:]: unrecognized selector sent to instance
0x5e02d70'
The first part of this error sometimes changes. Sometimes it's __NSCFType, sometimes it's CALayer. This error only happens when deleting Scenes. Adding Scenes is fine 100%.
I've read another post on stackoverflow that suggests these sorts of errors can come from memory management issues. I've double checked the code, and also run it through Instruments with the leak instrument. There are no leaks.
Is there anything else I can check? Any ideas?
Here's the relevant code..
From ScenesTableViewController.m:
// used to show/hide the add button
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
[super setEditing:editing animated:animate];
if(editing)
{
self.navigationItem.leftBarButtonItem = addButton;
}
else
{
self.navigationItem.leftBarButtonItem = nil;
}
}
// called when the add button is pressed
- (void)addAction {
NSEntityDescription *myContentEntity = [NSEntityDescription entityForName:#"Scene" inManagedObjectContext:managedObjectContext];
Scene *contentToSave = [[Scene alloc] initWithEntity:myContentEntity insertIntoManagedObjectContext:managedObjectContext];
[contentToSave setValue:#"New Scene" forKey:#"Name"];
[parentRoom addRoomToScenesObject:contentToSave];
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
[contentToSave release];
}
// delegate method that's being sent unrecognised selectors
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
// cell display code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier] autorelease];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *mo = nil;
NSString *temp = nil;
mo = [fetchedResultsController objectAtIndexPath:indexPath];
temp = [mo valueForKey:#"Name"];
[[cell textLabel] setText:temp];
}
// cell editing code
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object at the given index path.
[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
NSError *error;
if (![managedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// NSFetchedResultsController code
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Scene" inManagedObjectContext:managedObjectContext];
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:nameDescriptor, nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(SceneToRoom == %#)", parentRoom];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:entity];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[nameDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
That error is most likely coming from a NSFetchedResultsController that has a released delegate. Do you have a UIViewController that you released and didn't release the associated NSFetchedResultsController?
Even I faced the same issue. But for iOS 4.2 the problem is NSError is initialized, so it is treated as garbage and while updating/inserting we have
if (![managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
So in save the iOS treats the error as garbage and so the exception. Try to initialize it to nil. It solved my problem. The issue is seen only with 4.2iOS.