How to customize a UITableViewCell without any freezing in UITableView - iphone

I have customized the table view cell, created nib file and its corresponding .h and .m file. Everything works fine except slowness while scrolling, what could be the problem how can I avoid the freezing?
And
obj = [self.listData objectAtIndex: [indexPath row]];
if (obj != nil) {
static NSString *CellIdentifier;
if (obj->status == status1) {
CellIdentifier = #"Cell_italic";
} else if (obj->status == status2) {
CellIdentifier = #"Cell_cent";
} else {
CellIdentifier = #"Cell";
}
custom_cell *cell = (custom_cell*) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"custom_cell" owner:self options:nil];
cell = [nib objectAtIndex:0];
char file_path[MAX_PATH]; // Holds file path.
memset(file_path, 0, MAX_PATH);
// Get path
// Set Image
UIImage *cellImage = [UIImage imageWithContentsOfFile:fle_path];
if (cellImage != nil) {
[cell.image_view setImage:cellImage];
}
NSString *combined_name = [NSString stringWithCString:obj->combined_name encoding:NSASCIIStringEncoding];
NSString *email = [NSString stringWithCString:obj->email encoding:NSASCIIStringEncoding];
// Set name
if (obj->status == status1)
{
cell.name_label.text = combined_name;
cell.name_label.font = [UIFont fontWithName:#"Georgia-Italic" size:18];
cell.email_label.text = email;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
. . .
. . .
}
dequeueReusableCellWithIdentifier is always returning nil, Is it right? Without customization I have seen it was not nil many times, now it always returning nil.

image loading each-time that's why Table scroll freezing in UITableView you can implement lazy-loading many way refer bellow links and demos may be its helps you :-
https://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html
https://github.com/rs/SDWebImage //README Section says,how to use it in your app.
https://github.com/nicklockwood/AsyncImageView/
you can also load image in Background Process using UI thread
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(backgroundQueue,^{
// background process
image = [NSData dataWithContentsOfFile:imageName];
dispatch_async(mainQueue,^{
// always update GUI from the main thread
// uiimageview.image = image.... etc
});
});

Related

Table view mixes up images

I use SDWebImage to load and display async images in TableView. But sometimes when I scroll up and down fast, it mixes up all images and display it in other rows. This is my cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// Configure the cell.
MWFeedItem *item = [itemsToDisplay objectAtIndex:indexPath.row];
if (item) {
// Parse out Image URL for cell
if (cell.imageView.image == nil) {
NSError *error = NULL;
NSRegularExpression *regexImage = [NSRegularExpression regularExpressionWithPattern:#"(<img\\s[\\s\\S]*?src\\s*?=\\s*?['\"](.*?)['\"][\\s\\S]*?>)+?"
options:NSRegularExpressionCaseInsensitive
error:&error];
[regexImage enumerateMatchesInString:item.content
options:0
range:NSMakeRange(0, [item.content length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSString *src = [item.content substringWithRange:[result rangeAtIndex:2]];
NSLog(#"img src: %#", src);
[cell.imageView setImageWithURL:[NSURL URLWithString:src]];
}];}
if (cell.imageView.image == nil) {
cell.imageView.image = [UIImage imageNamed:#"video.png"];
}
return cell;
}
I don't know what is wrong, but I think its because I parse the image in the cell and it is not fast enough so it starts again and again. Can you please show me how to fix that
The high-level answer is, I believe, that when you scroll you are reusing the cells before you complete putting an image into them. That's what it means when I see this in my code, anyway.
I don't use SDWebImage and I don't know exactly what setImageWithURL variants are, but the github webpage has a how-to-use that says you can give it a completion block to execute when the image fetch is done (succeeds or fails).
So you need to check, when you finally have the image but before you put it in the UITableViewCell, that the cell is still assigned to the same indexPath as when you started to get the image. Since setImageWithURL seems to set the image always, you will have to put have a temporary UIImageView rather than directly in the cell. The docs I looked at had a method call with both placeHolderImage: and completed: Using that you'd do something like (code not compiler checked):
// before you go off to get the image, save the indexPath of the cell
NSIndexPath *originalIndexPath = indexPath;
UIImageView *tempImageView;
[tempImageView setImageWithURL:[NSURL URLWithString : [NSURL URLWithString:src]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
// completion block
// You probably want to check that it really succeeded, but if it did
// Now you have an image in the image parameter and probably in tempImageView.
// Check to see if the cell is still at the original indexPath
// If so, put the image in.
// If not, the row was scrolled out of sight and the cell has been reused
// so just drop the image on the floor -- it is no longer useful
NSIndexPath *currentIndexPath = [self.tableview indexPathForCell:cell]
if ([currentIndexPath isEqual originalIndexPath]) {
cell.imageView.image = image;
// or perhaps: cell.imageView.image = tempImageView.image;
}
}];
Change cell identifier from
static NSString *CellIdentifier = #"Cell";
to
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell%i",indexPath.row];

UISwitch untoggling on scroll of a UITableView

I have a UITableView with a UISwitch as the accessoryView. My problem is that if I toggle one of the switches then scroll so the switch it out of view it returns to its previous state.
Please see video.
Does anyone know why this might be and how to solve it?
Here is the code that adds the switch view and deals with its action.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"POICell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier ];
//set the cell text to the
cell.textLabel.text = [self.catNames objectAtIndex:[indexPath row]];
NSString *toggle = [self.toggleArray objectAtIndex:[indexPath row]];
//add switch
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//create an instance of the database oject
DataBase * dataBase = [[DataBase alloc] init];
//open the database connection
[dataBase openDB];
NSString *imageName = [dataBase getPinImageNameFromCatID:[self.catIDs objectAtIndex:[indexPath row]]];
//get the root file path for the images
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/pinImages/%#",documentsDirectory, imageName];
//add image
NSURL *imageURL = [NSURL fileURLWithPath:filePath];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
NSLog(#"%#",[self.catIDs objectAtIndex:[indexPath row]]);
UISwitch *switchView = [[UISwitch alloc] initWithFrame:CGRectZero];
cell.accessoryView = switchView;
[switchView addTarget:self action:#selector(switchChanged: ) forControlEvents:UIControlEventValueChanged];
if ([toggle isEqualToString: #"OFF"]) {
[switchView setOn:NO animated:NO];
}else{
[switchView setOn:YES animated:NO];
}
return cell;
}
- (void) switchChanged:(id)sender {
//get the switch that it was sent from
UISwitch *switchInCell = (UISwitch *)sender;
//get the cell it was sent from
UITableViewCell * cell = (UITableViewCell *) switchInCell.superview;
//get the row it was sent from
NSIndexPath * indexpath = [self.inputTableView indexPathForCell:cell];
//cast the indexpath to int
NSInteger variable = indexpath.row;
//set the filter as off in the user defualts.
[self.filterDic setValue:switchInCell.on ? #"ON" : #"OFF" forKey:[self.catIDs objectAtIndex:variable]];
//store the newdic in the user defualts
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
//save the dic to user defaults
[prefs setObject:self.filterDic forKey:#"pinFilters"];
}
Thanks for any help.
There are two issues with your code (at least :)
Let me start with the one that is confusing you.
You set the status of each witch based on toggle. And the value of toggle is tanken from an array self.toggleArray.
Fine so far.
But when a value changes and the action switchChanged is called then you update self.filterDic but you do not update self.toggleView.
And this causes the problem:
Next time when a cell becomes visible cellForRowAtIndexPath is called again and will set the value based on toggle wich is based on self.toggleArray. And that still has the old values in it ... you see?
You are making this mistake probably because you have not yet fully understood the cell recycle mechanism. And that is probably what causes the second issue that I identified. Let me try to explain.
iOS or cocoa respectively tries to allocate as view cell objects as nessessary. That means that a cell wich scrolls off the creen is added to a pool from which it can be re-used the next time when a (similar) sell is required. So each time when there is a need for a new cell (one that becomes visible) cellForRowAtIndexPath is called. Wihin that you fetch a cell using dequeueReusableCellWithIdentifier. If there is a cell in that pool that was initialized with the same re-use identfier then that one (or one of those) is returned to the caller.
In recent iOS (respectively SDK versions) versions a new cell will be allocated and returned if none of these cells exists. (And that is why Murali's suggestion would not work perfectly either)
In older versions you had to check cell for nil and alloc/init a new on in those cases.
After that you freely allocate new subview objects regardless whether the cell was re-cycled and already has those subviews or not. Then you add and add and add the same subviews again and again.
How can you solve this? There are, as usual, several ways of dealing with that:
First - Check whether the cell was re-used or not. Just check if the Switch is already there or not. For doing that you could tag it with some value different from 0 and fetch the subview with this tag. If you dont't get it then the cell is new and you have to create all the additional subvies.
Second - You could always erase all subviews from the cell right after fetching the cell with dequeueReusableCellWithIdentifier. That is the easiest solultion because you do not have to change mutch to your existing code. It is not the most performant solution though.
Third - The most elegant solution is probably to subclass UITableViewCell every time when you want to add custom elements to a cell. In that case its init method would be called only once upon creation (and not upon re-usage) and there you can programmatically add all the custom subviews. You can, of course, design the cell and add all subviews in IB as you can with every UIView object. Witin cellForRowAtIndexPath you would only have to care for setting the appropriate values.
Perhaps your cells are being recycled.
Have a look at the accepted answer here:
iphone : uitableview : contents of a cell change on scrolling
Please use this method..
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"POICell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UISwitch *switchView;
if (cell == nil)
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier ];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
switchView = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 100, 30)];//Change the frame
cell.accessoryView = switchView;
}
//set the cell text to the
cell.textLabel.text = [self.catNames objectAtIndex:[indexPath row]];
NSString *toggle = [self.toggleArray objectAtIndex:[indexPath row]];
//add switch
//create an instance of the database oject
DataBase * dataBase = [[DataBase alloc] init];
//open the database connection
[dataBase openDB];
NSString *imageName = [dataBase getPinImageNameFromCatID:[self.catIDs objectAtIndex:[indexPath row]]];
//get the root file path for the images
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/pinImages/%#",documentsDirectory, imageName];
//add image
NSURL *imageURL = [NSURL fileURLWithPath:filePath];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
NSLog(#"%#",[self.catIDs objectAtIndex:[indexPath row]]);
[switchView addTarget:self action:#selector(switchChanged: ) forControlEvents:UIControlEventValueChanged];
if ([toggle isEqualToString: #"OFF"]) {
[switchView setOn:NO animated:NO];
}else{
[switchView setOn:YES animated:NO];
}
return cell;
}

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.

UiTextField in a UITableViewCell get empty when scrolling

I'm using a UITableView grouped to display my application preferences.
I get the cell with this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger identifier = row + ((section + 1) * 10);
NSString *CellIdentifier = [NSString stringWithFormat:#"CustomCellIdentifier-%d",identifier];
LoginTableViewCell *cell = (LoginTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"LoginTableViewCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
cell.field.tag = identifier;
if (section == 0) {
switch (row) {
case 0:{
NSString *user = [[NSUserDefaults standardUserDefaults] valueForKey:#"username"];
cell.field.text = user;
cell.label.text = #"User";
break;
}
case 1:{
NSString *bundleName = [[NSBundle mainBundle] bundleIdentifier];
NSError *error = nil;
NSString *user = [[NSUserDefaults standardUserDefaults] objectForKey:#"username"];
NSString *pwd = [SFHFKeychainUtils getPasswordForUsername:user andServiceName:bundleName error:&error];
cell.field.text = pwd;
cell.field.secureTextEntry = YES;
cell.label.text = #"Password";
break;
}
case 2:{
NSString *numero = [[NSUserDefaults standardUserDefaults] valueForKey:#"numero"];
cell.field.text = numero;
cell.field.keyboardType = UIKeyboardTypePhonePad;
cell.label.text = #"Number";
break;
}
default:
break;
}
if (cell.field.text > 0) {
cell.field.enabled = NO;
cell.field.borderStyle = UITextBorderStyleNone;
}
} else {
switch (row) {
case 0:{
cell.field.text = [NSString stringWithFormat:#"+%#", [[NSUserDefaults standardUserDefaults] valueForKey:#"prefix"]];
cell.field.keyboardType = UIKeyboardTypePhonePad;
cell.label.text = #"Prefix";
break;
}
case 1:{
cell.field.text = [[NSUserDefaults standardUserDefaults] valueForKey:#"sender"];
cell.field.keyboardType = UIKeyboardTypeNamePhonePad;
cell.label.text = #"Sender";
break;
}
default:
break;
}
}
}
return cell;
}
The problem is that, when a row scrolls outside the window, the textfield get empty.
How can I prevent this?
I thought caching was the right solution, but even with the reusable identifier, I still have the same problem.
The approach you're using, that is loading the table cell from a nib file, requires - in order to have correct reusable cells support - to assign in the nib "Identifier" field the same string identifier you use in the code. That is, if your cell identifier used for cache deque is "MyCellId" then your UITableCellView "Identifier" field in the nib file must contain this same "MyCellId" identifier.
But with the approach you are following this is not possible, as the cell ID is dynamically generated and then you cannot of course dynamically assign the cell identifier to the nib-loaded cell.
In theory you must create a specific nib file for each of the MyCellId-%d identifiers you generate.
As I assume you have only one table cell defined, get rid of the dynamic identifier generation and use a static name and then assign this name to the "Identifier" field in the cell nib file .

Releasing static object

I know in vast of the cases I don't have to release static variable. However the following is the code for my model:
+ (UIImage*)imageForTag
{
static UIImage *imgTag;
if(imgTag == nil)
{
NSString* imageName = [[NSBundle mainBundle]
pathForResource:#"tag" ofType:#"png"];
imgTag = [[[UIImage alloc]
initWithContentsOfFile:imageName] autorelease];
}
return imgTag;
}
and here is my data table part
- (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];
}
if (indexPath.row == 0)
{
cell.imageView.image = [DataModel imageForSmtng];
}
else if(indexPath.row == 1)
{
cell.imageView.image = [DataModel imageForTag];
}
return cell;
This will crash on cell.imageView.image = [DataModel imageForTag] second time due to imageForTag is pointing to invalid address. If I add retain on that it will not crash. Is it wrong to remove autorelease from above and forget about imgTag references?
It is wrong. Because when you call autorelease on the imgTag variable, you just released the object it points to. But, the imgTag variable still points to that range of memory. So, when you call the imgTag again, it is not nil, it still points to something, an invalid thing.
So, the solution should be either:
1/ You shouldn't release it at all
2/ You have to release it manually when you think that it is a good time to release it. And then remember to do: imgTag = nil