i have 60 rows in an table view.i have an array named as "BundleImagesArray " with 60 bundle images names.So i am retrieving the images from bundle and creating thumbnail to it.
whenever binding each cell at first time ,i am storing the Thumbnail images in to an array.because of enabling the fast scrolling after bind each cell.i am utilizing the images from the array(without creating the thumbnail again).however the imageCollection array(which will store thumbnail images) is disorder some times
the Index path.row is coming as 1,2.....33,34,50,51..etc
its not an sequential order.so i am getting trouble with my imageCollection array which is used to store and retrieve images according to the index path.may i know what is the reason for that.can any one provide me a good solution for this.is there any way to get the indexpath.row as sequential order?
my cellForRowAtIndexPath code is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"IndexPath Row%d",indexPath.row);
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
if ([cell.contentView subviews])
{
for (UIView *subview in [cell.contentView subviews])
{
[subview removeFromSuperview];
}
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell setAccessoryType:UITableViewCellAccessoryNone];
Class *temp = [BundleImagesArray objectAtIndex:indexPath.row];
UIImageView *importMediaSaveImage=[[[UIImageView alloc] init] autorelease];
importMediaSaveImage.frame=CGRectMake(0, 0, 200,135 );
importMediaSaveImage.tag=indexPath.row+1;
[cell.contentView addSubview:importMediaSaveImage];
UILabel *sceneLabel=[[[UILabel alloc] initWithFrame:CGRectMake(220,0,200,135)] autorelease];
sceneLabel.font = [UIFont boldSystemFontOfSize:16.0];
sceneLabel.textColor=[UIColor blackColor];
[cell.contentView addSubview:sceneLabel];
//for fast scrolling
if([imageCollectionArrays count] >indexPath.row){
importMediaSaveImage.image =[imageCollectionArrays ObjectAtIndex:indexPath,row];
}
else {
//for start activity indicator
[NSThread detachNewThreadSelector:#selector(showallstartActivityBundle) toTarget:self withObject:nil];
NSData *datas = [self photolibImageThumbNailData:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:temp.fileName ofType:#"png" inDirectory:#"Images"]]];
importMediaSaveImage.image =[UIImage imageWithData:datas];
[imageCollectionArrays addObject:importMediaSaveImage.image];
//to stop activity indicator
[NSThread detachNewThreadSelector:#selector(showallstopActivityBundle) toTarget:self withObject:nil];
}
sceneLabel.text = temp.sceneLabel;
temp = nil;
return cell;
}
Getting the tableview to call you in a particular order is not the right solution. You need to be able to provide any cell it needs in any order.
I think the problem is that you are using an array and trying to access indexes that don't exist yet. You could use an NSMutableDictionary for your imageCollectionArrays (instead of NSArray) and store your data there, keyed by row (or NSIndexPath). They you can add or retrieve them in any order.
An NSArray is perfectly fine for UITableView rows. The rows are ordered, and an array is also ordered, so no problem there.
The problem in your code seems to be that you are using two arrays, one immutable and one mutable. You are adding objects to the mutable array, and so it is not exactly predictable where they will be added (because it is not predictable that the rows are going to be needed in order).
I recommend filling your NSMutableArray first with [NSNull null] objects, and then inserting the image at a specific point in the array:
// during initialization
for (int i=0; i<numberOfImages; i++) {
[imageCollectionArrays addObject:[NSNull null]];
}
// in cellForRowAtIndexPath:
[imageCollectionArrays replaceObjectAtIndex:indexPath.row
withObject:importMediaSaveImage.image];
Try that, see if it works.
Related
I'm loading pictures into a table view that correspond to the cell text, so each image is different. As the user scrolls down, iOS has to manually reload each image from the SSD, so scrolling is very choppy. How do I cache images or prevent the table view cells from needing to be recreated? Others have had their issues solved by using imageNamed: as iOS will automatically cache your images, but I am loading images from the documents directory, not the app bundle. What should I do? Thanks.
- (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 [issues count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
NSDictionary *dic = [self.issues objectAtIndex:indexPath.row];
cell.text = [dic objectForKey:#"Date"];
cell.imageView.image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/issues/%#/cover.png", documentsDirectory, [dic objectForKey:#"Directory Name"]]];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 150;
}
That array of dictionaries is your model, so it would be a natural place. The tricky part is making your model mutable. You can either make your model mutable in the class, or jump through the hoops when you cache the image. I'd recommend the former, but coded it here to match your existing code...
- (UIImage *)imageForIndexPath:(NSIndexPath *)indexPath {
NSDictionary *dic = [self.issues objectAtIndex:indexPath.row];
UIImage *image = [dic valueForKey:#"cached_image"];
if (!image) {
image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/issues/%#/cover.png", documentsDirectory, [dic objectForKey:#"Directory Name"]]];
NSMutableDictionary *updatedDictionary = [NSMutableDictionary dictionaryWithDictionary:dic];
[updatedDictionary setValue:image forKey:#"cached_image"];
NSMutableArray *updatedIssues = [NSMutableArray arrayWithArray:self.issues];
[updatedIssues replaceObjectAtIndex:indexPath.row withObject:[NSDictionary dictionaryWithDictionary:updatedDictionary]];
self.issues = [NSArray arrayWithArray:updatedIssues];
}
return image;
}
This will be choppy on the first scroll through the list, then smoother thereafter. If you'd like to have no first-time chop and incur a little latency before the view appears, you can walk your model ahead of time and force the load:
for (int i=0; i<self.issues.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
(void)[self imageForIndexPath:indexPath];
}
One last thing - Another solution is a mutable array to match your issues array. That may be just fine, but more often than not, I end up glad that I included some cached calculation with my model. If you find yourself using that issues array elsewhere, you'll be happy that you have the images all taken care of.
Along with caching you may also consider loading the images in background using Grand central dispatch. When the cell is loaded put a UIActivityIndicator then replace it with an image in a separate thread.
Also checkout this related answer for image stutter:
Non-lazy image loading in iOS
I have an app consisting of a TabBar with a few TabBarControllers. One Controller contains a very simple table, which is supposed to display the contents of a NSMutableDictionary. When you hit the appropriate button, the Dictionary is updated in a separate Controller and the view switches to the UITableViewController, displaying the newly updated table.
I can see the Dictionary being updated. But the TableView never reflects the changes. In fact, it seems to display the changes only the 1st time I enter that screen.
I have tried [self table.reloadData] and while it gets called, the changes aren't reflected to the UITableView.
Does anyone have any suggestions? I am happy to post code, but am unsure what to post.
Update: the table is updated and refreshed properly only the 1st time it is displayed. Subsequent displays simply show the original contents.
Background:
The tableview gets filled from a dictionary: appDelegate.currentFave. The tableview should get refreshed each time the ViewController is invoked by the TabBarController.
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"in viewWillAppear");
[super viewWillAppear:animated];
[self loadFavesFile];
[self.tableView reloadData];
}
// load the Favorites file from disk
- (void) loadFavesFile
{
// get location of file
NSString *path = [self getFavesFilePath];
// The Favorites .plist data is different from the Affirmations in that it will never be stored in the bundle. Instead,
// if it exists, then use it. If not, no problem.
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
// read Faves file and store it for later use...
NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
appDelegate.sharedData.dictFaves = tempDict;
// grab the latest quote. Append it to the list of existing favorites
NSString *key = [NSString stringWithFormat:#"%d", appDelegate.sharedData.dictFaves.count + 1];
NSString *newFave = [NSString stringWithFormat:#"%#", appDelegate.currentFave];
[appDelegate.sharedData.dictFaves setObject:newFave forKey:key];
} else {
NSLog(#"Favorites file doesn't exist");
appDelegate.sharedData.dictFaves = nil;
}
}
// this gets invoked the very first call. Only once per running of the App.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// reuse or create the cell
static NSString *cellID = #"cellId";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// allow longer lines to wrap
cell.textLabel.numberOfLines = 0; // Multiline
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.font = [UIFont fontWithName:#"Chalkduster" size:(16)];
cell.textLabel.textColor = [UIColor yellowColor];
// NOTE: for reasons unknown, I cannot set either the cell- or table- background color. So it must be done using the Label.
// set the text for the cell
NSString *row = [NSString stringWithFormat:#"%d", indexPath.row + 1];
cell.textLabel.text = [appDelegate.sharedData.dictFaves objectForKey:row];
return cell;
}
I found the problem. I was not properly initializing and assignng the TableView in my view controller. See below
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
tableView.backgroundColor=[UIColor blackColor];
self.view = tableView;
}
Assuming the code you have put up is correct, you want to use [self.table reloadData]. You have the . in the wrong place.
I had this same problem yesterday, for me it turned out I had set the wrong file owner in interface builder and hadn't set up the data source and delegates for the table view properly.
Try going into interface builder and right-clicking on the file owner, this should show you if anything isn't connected up properly.
You should make sure that your Interface Builder connections are set up properly, but what this problem really sounds like is that you have your UITableViewCell setup code in cellForRowAtIndexPath: inside your if(cell == nil) statement. Which it shouldn't be. Let me explain. If you have a list of cells, and you want to set the titles to each cell to a string in an array called myArray, right now your (incorrect) code looks like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
}
return cell;
}
Can you see the problem with that logic? The cell will only get an updated title if no reusable cell can be found, which, in your case, sounds like the situation. Apple says that you should create a 'new' cell each time cellForRowAtIndexPath: is called, which means that you put all of your setup code outside of the if(cell == nil) check.
Continuing with this example, the proper code would look like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
}
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
return cell;
}
This way, the cell gets assigned the proper string whether or not a reusable cell is found and so calling reloadData will have the desired effect.
Below is code for UITableView, But when i scroll its behaves weirdly (too annoying)... This problem is due to reuseIdentifier.... but dont know how to solve..
- (UITableViewCell *)tableView:(UITableView *)tableView1 cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView1 dequeueReusableCellWithIdentifier:CellIdentifier];
NSInteger imgTag = 1;
NSInteger lblTag = 2;
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(2, 2, 52, 52)];
// Image:[UIImage imageNamed:[self.glassType objectAtIndex:indexPath.row]]];
imgView.tag = imgTag;
[cell.contentView addSubview:imgView];
[imgView release];
UILabel *lblName = [[UILabel alloc] initWithFrame:CGRectMake(60, cell.frame.size.height/4, 200, 21)];
// lblName.text = [self.glassName objectAtIndex:indexPath.row];
lblName.tag = lblTag;
[cell addSubview:lblName];
[lblName release];
}
NSInteger imgIndex = 2;
NSInteger lblIndex = 3;
((UIImageView *)[cell viewWithTag:imgTag]).image = [[self.glassType objectAtIndex:indexPath.row] objectAtIndex:imgIndex];
((UILabel *)[cell viewWithTag:lblTag]).text = [[self.glassName objectAtIndex:indexPath.row] objectAtIndex:lblIndex];
return cell;
}
- (void)tableView:(UITableView *)tableView1 didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView1 cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
How to make Cell for row at index so that it remains constant even when scrolled??? Also how to make single selection in UITableView??
The answer is that you should not add subviews to your table cells outside of the "if (cell == nil) { ..." clause or they get added over and over again to the same cell when it gets re-used.
See my answer to this question for a more detailed explanation, including code for how to fix it:
cellForRowAtIndexPath memory management
You also cannot store state in table cells because as soon as they scroll offscreen they are recycled and re-appear at a different index in your table. You need to set up an array of model objects to store state for your table cells (such as what their accessory type should be). A more detailed explanation can be found in my answer to this question:
Looping through UITableViewCells of a UITableView
If you fix how you are adding subviews to the cells, and store your "ticked" state in an array of model objects as well as setting the cell.accessoryType (so that it can be restored when the cell is dequeued), then your approach to row selection is otherwise correct.
So put this in your tableView:cellForRowAtIndexPath: method, just before the return cell;:
MyModelObject *object = [self.arrayOfModelObjects objectAtIndex:indexPath.row];
BOOL isChecked = object.checked;
cell.accessoryType = isChecked? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
And in your tableView: didSelectRowAtIndexPath: method, get rid of the current logic and replace it with:
- (void)tableView:(UITableView *)tableView1 didSelectRowAtIndexPath:(NSIndexPath *)indexPath
for (int i = 0; i < [self.arrayOfModelObjects count]; i++)
{
MyModelObject *object = [self.arrayOfModelObjects objectAtIndex:i];
object.checked = (i == indexPath.row); // only check the one we just tapped
}
//refresh table to update the accessory views for all rows
[tableView1 reloadData];
}
Obviously replace the arrayOfModelObjects with your own model implementation. You could just use an array of NSNumber objects containing bools if you don't want to create a custom class for this purpose.
The recycling queue is like a pool where previously created Cells are stored before to reuse them. For example when you scrolls up, at the moment the cell disappears above, it is stored in the queue and becomes available for the cell that will appear at the bottom. Ok ?
Actually the number of cells really created is exactly the max simultaneous cell you can display in your table (in most cases from 3 to 8). In other words your if (cell == nil) code is executed (more or less from 3 to 8 times) at the first reloadData to create the pool of cells your table needs.
Then all you make on a cell is kept as it and appears again when you dequeue it. It's now easy to understand that, in your code, you have to make all strictly row-dependant settings outside the if (cell == nil) block. The same way, do not add subViews outside the if (cell == nil) block, you can imagine the thousands of subview you will add each time you reset a dequeued cell !
Tip: if you need some custom cleanup before reusing a cell (like to set an image to blank), you can create a custom UITableviewCell class and implements the prepareForReuse method.
Is it clear ?
Always reload your tableView in viewWillAppear method instead of viewDidLoad.
(void) viewWillAppear:(BOOL)animated{
[self.tableView reloadData];
}
This avoids most of all unexpected and annoying problems. :)
Looking for help on how to properly load the data for a Grouped Table View. I am using following code to load the data which I think should prepare an array "details" for "cellForRowAtIndexPath". However when I run the program I get the same data for all groups and it happens to be the last row in my data (which is in an NSArray called "Users"). Obviously I am doing something wrong but not sure what... please help.
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSRange aRange;
aRange.length = 9; aRange.location = 16;
NSString *users = [self.Users objectAtIndex: section];
NSString *subtitle = [NSString stringWithString:[users substringWithRange:aRange]];
details = [[NSArray arrayWithObjects:subtitle, subtitle, subtitle, nil] retain];
return [details count];
}
The data loading routine is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
UIImage *cellImage = [UIImage imageNamed:#"world.png"];
cell.imageView.image = cellImage;
cell.textLabel.text = [details objectAtIndex:indexPath.row];
return cell;
}
From what you've posted, details array is being reset every time the table views asks its data sources for the number of rows in a section. details is last set when -tableView:numberOfRowsInSection: is last called i.e. for the last section. Once set, the same array provides the data for cell.textLabel.text for all the sections. So you are getting incorrect data.
It would be appropriate to calculate and store all the details arrays in a different array, say detailsArray.
The data can then be accessed them like
[[detailsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]
Another problem with your code is that every time numberOfRowsInSection is called, data is being leaked as details is being reset without the earlier object being released.
I am parsing an RSS feed, and then caching the images from the rss feed and then displaying them in the cell's imageview. However the method I am using, slows down the rss feed's parse time, and slows down the TableView's scroll time. Please could you tell me how I could speed up this process. One of the image links is: http://a1.phobos.apple.com/us/r1000/009/Video/95/5d/25/mzl.gnygbsji.71x53-75.jpg, and one of the rss feeds I am trying to parse is: http://itunes.apple.com/au/rss/topmovies/limit=50/xml. Here is the code I am using to cache the images:
- (UIImage )getCachedImage: (NSString)url
{
UIImage* theImage = [imageCache objectForKey:url];
if ((nil != theImage) && [theImage isKindOfClass:[UIImage class]]) {
return theImage;
}
else {
theImage = [UIImage imageWithData: [NSData dataWithContentsOfURL:[NSURL URLWithString: url]]];
[imageCache setObject:theImage forKey:url];
return theImage;
}
}
And the code I am using to get the images is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
int wierd = storyIndex *6;
cell.textLabel.text = [[stories objectAtIndex: storyIndex] objectForKey: #"songtitle"];
cell.detailTextLabel.text = [[stories objectAtIndex:storyIndex] objectForKey:#"artist"];
if ([imageLinks count] != 0) {
cell.imageView.image = [self getCachedImage:[imageLinks objectAtIndex:wierd]];
}
return cell;
}
As you can probably see, I am using an NSMutableArray called imageLinks, to store the imageLinks. However I am getting three image links from the rss feed, which means if I try to get the cached image: [imageLink objectAtIndex:storyIndex], the images are in the wrong places, but if I get the cached image: [imageLink objectAtIndex:wierd], it seems to work perfectly. So if you can see a fix to that, it would be great.
Thanks in advanced.
Your problem is that you're using dataWithContentsOfURL which is a blocking API. This means that it is performed on the main thread along with your UI and will block your UI until it completes. This is bad.
You should look into the NSURLConnection class and it's delegate protocol, NSURLConnectionDelegate to do data downloads asynchronously without manually spawning and managing new threads.