I subclassed UITableViewCell. Basically what happens is that when you press the UITableViewCell drawing on the cell's layer occurs causing the cell to appear different. However, when I delete a cell, that drawing drops to the cell below it. This to me seems to indicate that the cell's format is getting re-used as would be normal. Thus I redrew the cell in CellForRowAtIndexPath as one can see below...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
AGProgressViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
//NSLog(#"progress value = %f", [cell.progress floatValue]);
if (!cell) {
cell = [[AGProgressViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
Task * task = nil;
if (indexPath.section == 0){
task = [self.tasksByDay[#"anyday"] objectAtIndex:indexPath.row];
} else if (indexPath.section == 1){
task = [self.tasksByDay[#"monday"] objectAtIndex:indexPath.row];
} else if (indexPath.section == 2){
task = [self.tasksByDay[#"tuesday"] objectAtIndex:indexPath.row];
} else if (indexPath.section == 3){
task = [self.tasksByDay[#"wednesday"] objectAtIndex:indexPath.row];
} else if (indexPath.section == 4){
task = [self.tasksByDay[#"thursday"] objectAtIndex:indexPath.row];
} else if (indexPath.section == 5){
task = [self.tasksByDay[#"friday"] objectAtIndex:indexPath.row];
} else if (indexPath.section == 6){
task = [self.tasksByDay[#"saturday"] objectAtIndex:indexPath.row];
} else if (indexPath.section == 7){
task = [self.tasksByDay[#"sunday"] objectAtIndex:indexPath.row];
}
cell.progress = [NSNumber numberWithFloat:([task.timeSpent floatValue]/[task.time floatValue])];
// this is calling the redrawing method in the cell
[cell drawFillInAtPercent:[task.timeSpent floatValue]/[task.time floatValue]];
//NSLog(#"progress value = %f vs. time spent = %f", [cell.progress floatValue], [task.timeSpent floatValue]/[task.time floatValue]);
cell.textLabel.text = task.name;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d minutes",
[task.time intValue] - [task.timeSpent intValue]];
return cell;
}
However, this did not fix the problem. All those NSLogs showed the cell being at the right level for every re-drawing. This means, for some reason, the cell that is getting deleted is not getting called in the cellForRowAtIndex path. The weird thing is that the text labels are changing, simply the custom drawing that I do in the UITableViewCell subclass is not changing.
This is the method that I am calling there in the subclass.
-(void) drawFillInAtPercent: (float) percent{
//if (percent > 0){
NSLog(#"progress layer at percent %f", percent);
_progressLayer = [CAGradientLayer layer];
_progressLayer.frame = CGRectMake(self.bounds.origin.x,
self.bounds.origin.y,
self.bounds.size.width * percent,
self.bounds.size.height);
_progressLayer.colors = #[(id)[[UIColor colorWithRed:0/255.0 green:0/250.0 blue:250.0/255.0 alpha:1.0f] CGColor],
(id)[[UIColor colorWithRed:150.0/200.0 green:150.0/200.0 blue:150.0/200.0 alpha:.5] CGColor],
(id)[[UIColor colorWithRed:200.0/200.0 green:200.0/200.0 blue:200.0/200.0 alpha:.5] CGColor],
(id)[[UIColor colorWithWhite:0.3f alpha:0.1f] CGColor]];
_progressLayer.locations = #[#0.00f, #0.2f, #0.90f, #1.00f];
[self.layer insertSublayer:_progressLayer atIndex:1];
//}
}
I have no idea what is happening, and I don't seem to be able to access the reused cell in order to redraw it.
These are the deletion methods:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self deactivateTimers];
NSArray * days = [NSArray arrayWithObjects: #"anyday", #"monday", #"tuesday", #"wednesday", #"thursday", #"friday", #"saturday", #"sunday", nil];
Task * task = self.tasksByDay[days[indexPath.section]][indexPath.row];
if ([task.weekly boolValue]){
task.finished = [NSNumber numberWithBool:1];
} else {
[managedObjectContext deleteObject:task];
}
[self.managedObjectContext save:nil];
[self grabTasksFromContext];
}
}
-(void) grabTasksFromContext{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Task"
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray * managedObjects = [NSMutableArray arrayWithArray:[managedObjectContext
executeFetchRequest:fetchRequest error:&error]];
int numObjects = [managedObjects count];
for (int i = 0; i < numObjects; i ++){
Task * task = [managedObjects objectAtIndex:i];
NSLog(#"name %#", task.name);
// if the task is finished we don't want it to be displayed in the list
if ([task.finished boolValue]){
NSLog(#"finished");
[managedObjects removeObject:task];
i -= 1;
numObjects -= 1;
}
}
self.tasks = managedObjects;
// This implementation is pretty ugly
// I'm sorry about that and will fix it in the future
// probably the more attractive way to do this is to make an array of the days, and then cycle through that and check through the array of tasks
monday = tuesday = wednesday = thursday = friday = saturday = sunday = anyday = 0;
NSMutableArray * mondayArray = [[NSMutableArray alloc] init];
NSMutableArray * tuesdayArray = [[NSMutableArray alloc] init];
NSMutableArray * wednesdayArray = [[NSMutableArray alloc] init];
NSMutableArray * thursdayArray = [[NSMutableArray alloc] init];
NSMutableArray * fridayArray = [[NSMutableArray alloc] init];
NSMutableArray * saturdayArray = [[NSMutableArray alloc] init];
NSMutableArray * sundayArray = [[NSMutableArray alloc] init];
NSMutableArray * anydayArray = [[NSMutableArray alloc] init];
for (Task * task in self.tasks){
if ([task.day isEqualToString:#"monday"]){
mondayArray[monday] = task;
monday++;
} else if ([task.day isEqualToString:#"tuesday"]){
tuesdayArray[tuesday] = task;
tuesday++;
} else if ([task.day isEqualToString:#"wednesday"]){
wednesdayArray[wednesday] = task;
wednesday++;
} else if ([task.day isEqualToString:#"thursday"]){
thursdayArray[thursday] = task;
thursday++;
} else if ([task.day isEqualToString:#"friday"]){
fridayArray[friday] = task;
friday++;
} else if ([task.day isEqualToString:#"saturday"]){
saturdayArray[saturday] = task;
saturday++;
} else if ([task.day isEqualToString:#"sunday"]){
sundayArray[sunday] = task;
sunday++;
} else {
anydayArray[anyday] = task;
anyday++;
}
}
self.tasksByDay = [[NSDictionary alloc] initWithObjectsAndKeys: mondayArray,#"monday",
tuesdayArray, #"tuesday", wednesdayArray, #"wednesday", thursdayArray, #"thursday",
fridayArray, #"friday", saturdayArray, #"saturday", sundayArray, #"sunday",
anydayArray, #"anyday", nil];
[self.tableView reloadData];
}
Any help or thoughts on what is happening would be appreciated.
However, when I delete a cell, that drawing drops to the cell below it.
This is the key observation: it indicates to me that at the time the redraw is happening, the model (i.e. your self.tasksByDay[dayName]) has not been updated yet. When a cell at a certain row is deleted, the taskByDay for the corresponding day needs to be updated to remove the corresponding row from the NSArray. If this does not happen, the data for the deleted task would influence the drawing of a cell at the next index, thus the visuals wold appear to "drop" by one row. From your description it sounds like this is precisely what is happening.
You need to make sure that by the time the table is refreshed (or the notification of a cell deletion is sent to the UITableView) the model has been updated already to not have the row being deleted. This way the table visuals would update as you expect them to.
Not directly related to the problem, but if you create an array
NSArray *dayName = #[#"anyday", #"monday", #"tuesday", #"wednesday", etc.];
you can replace the long chain of ifs with
task = [self.tasksByDay[dayName objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
The other problem in your code is that every time that you call drawFillInAtPercent:, a new CAGradientLayer is added. As you scroll up and down, reused cells accumulate new layers without ever getting rid of the previously added ones. You need to change your code to add the gradient layer only once, and then reusing the existing one inside your drawFillInAtPercent: method. For example, you can add the layer in the designated initializer of the AGProgressViewCell, assigning it to _progressLayer instance variable, and adding it to the layer hierarchy once. From then on, drawFillInAtPercent: would change the existing _progressLayer, rather than creating new ones each time.
Related
I have two tables, one table for usernames, and one table for scores. I populate those tables with two arrays. The tables need to be in descending order based on the scores. I sort the array with the scores in it, but I am not sure how I can arrange the usernames to stick with their score, they are in a separate array, and a separate table from the scores table. Here is my code:
dictionary = [NSDictionary dictionaryWithObjects:matchesForUser forKeys:tableData];
sortedFirstArray = [dictionary allKeys];
sortedSecondArray = [dictionary objectsForKeys:sortedFirstArray notFoundMarker:[NSNull null]];
sortedSecondArray = [sortedSecondArray sortedArrayUsingSelector: #selector(compare:)];
I need the sortedFirstArray values to stick with their respective sortedSecondArray values in terms of their order in each of their arrays.
UPDATE
My code attempting to do the sorting:
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
entries = [NSMutableArray new];
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
[tableData addObject:[object valueForKey:#"username"]];
[matchesForUser addObject:[object valueForKey:#"matches"]];
NSMutableDictionary* entry = [NSMutableDictionary new];
entry[#"username"] = [object valueForKey:#"username"];
entry[#"matches"] = [object valueForKey:#"matches"];
[entries addObject:entry];
//transfer = entries;
}
transfer = [entries sortedArrayUsingComparator:^NSComparisonResult(NSDictionary* a, NSDictionary* b) {
NSDate *first = [a objectForKey:#"matches"];
NSDate *second = [b objectForKey:#"matches"];
NSLog(first);
NSLog(second);
return [first compare:second];
}];
//dictionary = [NSDictionary dictionaryWithObjects:matchesForUser forKeys:tableData];
//sortedFirstArray = [dictionary allKeys];
//sortedSecondArray = [dictionary objectsForKeys:sortedFirstArray notFoundMarker:[NSNull null]];
//sortedSecondArray = [sortedSecondArray sortedArrayUsingSelector: #selector(compare:)];
[_tableView reloadData];
[_tableViewScore reloadData];
}else{
NSLog([error description]);
}
NSLog(#"***tabledata***");
NSLog([NSString stringWithFormat:#"%lu", (unsigned long)[tableData count]]);
NSLog(#"***matchesdata***");
NSLog([NSString stringWithFormat:#"%lu", (unsigned long)[matchesForUser count]]);
});
}];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(tableView.tag == 1) {
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
cell.textLabel.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:16.0];
cell.textLabel.textColor = [UIColor colorWithRed:218.0f/255.0f green:247.0f/255.0f blue:220.0f/255.0f alpha:1.0f];
cell.backgroundColor = [UIColor colorWithRed:153.0f/255.0f green:211.0f/255.0f blue:212.0f/255.0f alpha:1.0f];
cell.layoutMargins = UIEdgeInsetsZero;
cell.preservesSuperviewLayoutMargins = NO;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
UILabel *contentV = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 230, 44)];
contentV.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:16.0];
contentV.textColor = [UIColor colorWithRed:218.0f/255.0f green:247.0f/255.0f blue:220.0f/255.0f alpha:1.0f];
contentV.backgroundColor = [UIColor colorWithRed:153.0f/255.0f green:211.0f/255.0f blue:212.0f/255.0f alpha:1.0f];
cell.contentView.layoutMargins = UIEdgeInsetsZero;
NSString *username2 = [[transfer objectAtIndex:indexPath.row] valueForKey:#"username"];
NSLog(#"***username***");
NSLog(username2);
contentV.text = username2;
[cell.contentView addSubview:contentV];
return cell;
}
else {
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
cell.textLabel.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:16.0];
cell.textLabel.textColor = [UIColor colorWithRed:153.0f/255.0f green:211.0f/255.0f blue:212.0f/255.0f alpha:1.0f];
cell.backgroundColor = [UIColor colorWithRed:218.0f/255.0f green:247.0f/255.0f blue:220.0f/255.0f alpha:1.0f];
cell.textLabel.textAlignment = NSTextAlignmentCenter;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
NSString *matchAmount = [[transfer objectAtIndex:indexPath.row] valueForKey:#"matches"];
NSLog(#"***matchamount***");
NSLog(matchAmount);
cell.textLabel.text = matchAmount;
return cell;
}
}
Instead of having two separate arrays and going trough the trouble of sorting one of them directly and try to figure out which entries of the other correspond to which in the sorted one, you should instead keep the paired entries of both arrays together in one entity (dictionary or instance of a custom class), and group those in your (single) array:
The actual property names etc. will vary for your code, but the general idea is like this:
self.entries = [NSMutableArray new];
for (int i=0; i < userNameArray.count; i++){
NSMutableDictionary* entry = [NSMutableDictionary new];
entry["userName"] = [userNameArray objectAtIndex: i];
entry["score" ] = [scoreArray objectAtIndex: i];
// ^ TODO: Make sure scoreArray has at least as many elements
// as userNameArray!
[self.entries addObject: entry];
}
self.entries = [self.entries sortedArrayUsingComparator:^NSComparisonResult(NSDictionary* a, NSDictionary* b) {
NSDate *first = [a objectForKey:"score"];
NSDate *second = [b objectForKey:"score"];
return [first compare:second];
}];
// (...)
UITableViewCell* tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath}) indexPath
{
NSDictionary* entry = [self.entries objectAtIndex: indexPath.row];
if (tableView == self.userNameTableView) {
// (dequeue user cell...)
cell.titleLabel.text = [entry objectForKey: "userName"];
return cell
}
else{
// (dequeue score cell...)
cell.titleLabel.text = [entry objectForKey: "score"];
return cell
}
}
Credit goes to NicolasMiari for helping me figure most of this out. Something weird was happening with the way the comparison was being made - and it was producing an unsorted result. This is how I sorted:
NSSortDescriptor * descriptor = [[NSSortDescriptor alloc] initWithKey:#"matches" ascending:NO selector:#selector(localizedStandardCompare:)];
NSArray *entrieshold = [entries sortedArrayUsingDescriptors:#[descriptor]];
transfer = [entrieshold copy];
It seemed like the most important thing for me was selector:#selector(localizedStandardCompare:). My use of copy also may be important...but I don't think so.
Im using the following code to load data into tableview. Following is my code,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"any-cell"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"any-cell"];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.layer.borderWidth = 1.0;
cell.layer.cornerRadius = 10;
cell.layer.borderColor = [UIColor blackColor].CGColor;
UILabel* productAmountTextLabel = [[UILabel alloc]init];
productAmountTextLabel.font = [UIFont fontWithName:#"HelveticaNeue" size:10];
productAmountTextLabel.frame = CGRectMake(0, 0, 100, 30); // for example
productAmountTextLabel.tag = 10000; // any number
[cell.contentView addSubview:productAmountTextLabel];
}
UILabel* lbl = (UILabel*)[cell.contentView viewWithTag: 10000];
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
lbl.text = [device valueForKey:#"amount"];
return cell;
}
The problem is that each cells of tableview displays same value. Why is that so?
Following is my viewdDidLoad method,
- (void)viewDidLoad
{
segmentedControl = [[URBSegmentedControl alloc]initWithTitles:titles icons:icons];
NSError *Error = nil;
APIRequest *apiRequest = [[APIRequest alloc]init];
[apiRequest getPendingData];
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"PendingShipmentDetails"];
self.devices = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"PendingShipmentDetails" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&Error];
amountArray = [[NSMutableArray alloc] init];
for (NSManagedObjectContext * info in fetchedObjects)
{
[amountArray addObject:[info valueForKey:#"amount"]];
}
segmentedControl.segmentBackgroundColor = [UIColor colorWithRed:86/255.0f green:199/255.0f blue:188/255.0f alpha:1];
[segmentedControl addTarget:self action:#selector(handleSelection:) forControlEvents:UIControlEventValueChanged];
NSLog(#"%#",self.devices);
self.completedOrdersTableView.hidden = YES;
[self.view addSubview:segmentedControl];
[super viewDidLoad];
}
Im fetching values over there and adding it to an array.
within viewDidLoad , it has unique set of datas but within cellForRowAtIndexPath, it has sameset of datas being repeated multiple times.
Thanks to Michaƫl Azevedo for helping me debug the issue. The way I debugged was, I tried logging indexpath.row and indexpath.section. I notices that, row is always 0 and section is dynamic (value changes).
In cellForRowAtIndexPath i was setting the values with reference to indexpath.row which is going to 0 always.
Then i changed my code as follows,
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.devices count];
}
so now the numberOfRowsInSection will not be zero anymore. Hence while accessing it, It wont be fetching the same set of value of multiple times.
I think the problem might be your array named "self.devices". All your label.text is coming from your device model. You can try printing self.devices to take a look at each object in your array.
I'm thinking that this is an issue with reusing cells but I can't figure this out and would appreciate some additional eyes on it. I have a uitableviewcell subclass that compares two values, if one value is higher it changes the cell background to red, else it changes it to white. As I scroll, some cells are white that should be red and vice versa.
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
tut_MaintListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"maintCell" forIndexPath:indexPath];
// Configure the cell...
MaintItem *mItem = [self.fetchedResultsController objectAtIndexPath:indexPath];
[cell configureCellForEntry:mItem sendCar:self.carDetail];
return cell;
}
UITableViewCell Subclass
- (void)configureCellForEntry:(MaintItem *)mItem sendCar:(Car *)carDetails
{
self.itemLabel.text = [mItem valueForKey:#"item"];
self.actionLabel.text = [mItem valueForKey:#"action"];
self.engineLabel.text = [mItem valueForKey:#"engineCode"];
self.maintIDLabel.text = [[mItem valueForKey:#"maintID" ]stringValue];
// Grab the mileages recorded in the log for this maint item and turn it into a sorted array
NSArray *result = [[mItem.toLog valueForKey:#"mileage"] sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"" ascending:YES]]];
// Determine mileage of next service
NSString *nextServiceMileage = [NSString stringWithFormat:#"%d", [mItem.intMileage intValue] + [[result lastObject] intValue]];
nextServiceMileageNS = #([nextServiceMileage intValue]);
if ([mItem.frequencyID isEqualToNumber:[NSNumber numberWithInt:3]])
{
NSString *timing = [[NSString alloc] initWithFormat:#" %# Once at %# miles or %# months", [mItem valueForKeyPath:#"frequencyID"], [mItem valueForKeyPath:#"intMileage"], [mItem valueForKeyPath:#"intMonth"]];
NSString *howOften = [[NSString alloc] initWithFormat:#" %#", timing];
self.howOftenLabel.text = howOften;
if (carDetails.mileage > nextServiceMileageNS)
{
self.backgroundColor = [UIColor redColor];
}
else
{
self.backgroundColor = [UIColor whiteColor];
}
}
else if ([mItem.frequencyID isEqualToNumber:[NSNumber numberWithInt:4]])
{
NSString *timing = [[NSString alloc] initWithFormat:#" %# Every %# miles or %# months, due at %# ", [mItem valueForKeyPath:#"frequencyID"], [mItem valueForKeyPath:#"intMileage"], [mItem valueForKeyPath:#"intMonth"], nextServiceMileage];
NSString *howOften = [[NSString alloc] initWithFormat:#" %#", timing];
self.howOftenLabel.text = howOften;
if (carDetails.mileage > nextServiceMileageNS)
{
self.backgroundColor = [UIColor redColor];
}
else
{
self.backgroundColor = [UIColor whiteColor];
}
}
else
{
NSString *howOften = [[NSString alloc] initWithFormat:#" %#", [mItem valueForKeyPath:#"frequencyID"]];
self.howOftenLabel.text = howOften;
}
}
The Solution is: you have to set the backgroundColor in the else part too. Better solution would be,
UIView *backgroundView;
if (condition_1) {
backgroundView = [UIView new];
[backgroundView setBackgroundColor:[UIColor whiteColor]];
} else if (condition_2) {
backgroundView = [UIView new];
[backgroundView setBackgroundColor:[UIColor redColor]];
} else {
// here you can set or leave it.
}
[self setBackgroundView:backgroundView];
hope it will work for you...
The solution was that I was comparing two NSnumber objects in the if statement. I changed
if (carDetails.mileage > nextServiceMileageNS)to if ([carDetails.mileage intvalue] > [nextServiceMileageNS intvalue]) and now it worked correctly. The way the random background were applied it seemed to be a cell reuse issue.
i have a UITebleView with costume UITableViewCells. Every time I refresh the them the content is reordering itself. anyone know why?
I am fatching the data from a JSON, I dont do some sort of sorting, I just display the data acording to the TableViewCell indexpath.row
And this is the code I set the UITableViewCell content:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *costumeCell = #"Cell";
StoreCell *cell = [tableView dequeueReusableCellWithIdentifier:costumeCell];
if (!cell) {
cell = [[StoreCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:costumeCell];
}
NSDictionary *dict;
dict = [application objectAtIndex:indexPath.row];
[downloadQueue addOperationWithBlock:^{
name = [dict objectForKey:#"name"];
detileName = [dict objectForKey:#"detailName"];
itmsLink = [dict objectForKey:#"itms-serviceLink"];
icon = [dict objectForKey:#"icon"];
developer = [dict objectForKey:#"developer"];
version = [dict objectForKey:#"version"];
category = [dict objectForKey:#"category"];
rating = [dict objectForKey:#"rating"];
ratingNumbers = [dict objectForKey:#"ratingNumber"];
description = [dict objectForKey:#"description"];
developerEmails = [dict objectForKey:#"developerEmail"];
cell.AppName.text = name;
cell.category.text = category;
cell.rater.text = [NSString stringWithFormat:#"(%#)", ratingNumbers];
if ([rating intValue] == 1) {
cell.rating.image = [UIImage imageNamed:#"1.png"];
}
if ([rating intValue] == 2) {
cell.rating.image = [UIImage imageNamed:#"2.png"];
}
if ([rating intValue] == 3) {
cell.rating.image = [UIImage imageNamed:#"3.png"];
}
if ([rating intValue] == 4) {
cell.rating.image = [UIImage imageNamed:#"4.png"];
}
cell.itms = itmsLink;
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:icon]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
if(error)
{
// Error Downloading image data
cell.AppIcon.image = [UIImage imageNamed:#"placeholder.png"];
}
else
{
[cell.AppIcon setImage:[UIImage imageWithData:data]];
}
}];
cell.AppIcon.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:icon]]];
cell.number.text = [NSString stringWithFormat:#"%li", (long)indexPath.row + 1];
}];
cell.AppIcon.layer.masksToBounds = YES;
cell.AppIcon.layer.cornerRadius = 16.0;
cell.installButton.layer.masksToBounds = YES;
cell.installButton.layer.cornerRadius = 5.0f;
cell.installButton.layer.borderColor = [UIColor darkGrayColor].CGColor;
cell.installButton.layer.borderWidth = 1.0f;
return cell;
}
Since you're fetching from JSON, the server you got it from may not sort your data for you. Course its impossible for us to know what its doing without you divulging into the the server's APIs.
What you can do, without giving further info is just do a sort after you receive new data from the server:
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortedApplicationArray = [application sortedArrayUsingDescriptors:#[descriptor]];
application = sortedApplicationArray;
Just don't put that code inside your cellForRowAtIndexPath, cos it'll do a sort each time a row is created!
I think I know what the problem is.
You have an asynchronous operation in your cellForRowAtIndexPath. You should set the cell UI elements directly with the dict object and outside the operation queue.
The only thing that looks like it needs to be done asynchronously is the image download. I recommend you use SDWebImage from github for that, it handles the download in its own queue and caches the image in memory and on disk.
I have a very weird behavior with a UITableViewController in my project.
Normally it works perfectly but in one specific case it doesn't.
I have a dynamic table view with one custom type of cell. After filling all the data into the data source the table shows all the content correctly. There is a Pull-to-Refresh that updates the data source and table correctly. There are some filter buttons that update the only section with an animation correctly.
But if I click on one the detail view pushes into and if I go back click on one of these filter buttons again all the table view cells update except the ones I clicked. But if I click on this one again the detail view appears with the data of the cell that used to be there.
So the data updates just fine but the visible doesn't.
I would appreciate any suggestions. Thank you
P.S: Yes I do call the deselectRowAtIndexPath: method in the didSelectRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CellIdentifier = #"BANF";
BANFCell *cell = (BANFCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
if (cell == nil) {
cell = [[BANFCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
// collect required data
Requisition *req;
// for right section
if ([self.tableView numberOfSections] == 1) {
req = [recent objectAtIndex:indexPath.row];
} else {
if (indexPath.section == 1) {
req = [recent objectAtIndex:indexPath.row];
} else {
req = [notSent objectAtIndex:indexPath.row];
}
}
NSMutableArray *shortTexts = [[NSMutableArray alloc] init];
// get description text and sort short texts ascending
// also the amount and currency
NSString *reqDescript;
NSString *amount;
NSString *currency;
for (Trait *trait in req.traits) {
if ([trait.name isEqualToString:#"DESCRIPTION"] && trait.value.length > 0) {
reqDescript = trait.value;
}
if ([trait.name isEqualToString:#"TOTAL_AMOUNT"] && trait.value.length > 0) {
amount = trait.value;
}
if ([trait.name isEqualToString:#"CURRENCY"] && trait.value.length > 0) {
currency = trait.value;
}
}
NSString *amountAndCurreny;
if (amount) {
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[currencyFormatter setCurrencyCode:currency];
amountAndCurreny = [currencyFormatter stringFromNumber:[NSNumber numberWithDouble:amount.doubleValue]];
} else
amountAndCurreny = #"";
cell.amountLabel.text = amountAndCurreny;
NSArray *descriptors = [NSArray arrayWithObjects:[[NSSortDescriptor alloc] initWithKey:#"itm_number"
ascending:YES], nil];
NSArray *orderedArray = [req.positions sortedArrayUsingDescriptors:descriptors];
for (Position *position in orderedArray) {
for (Trait *trait in position.traits) {
if ([trait.name isEqualToString:#"SHORT_TEXT"] && trait.value.length > 0) {
[shortTexts addObject:trait.value];
}
}
}
UIImage *bgImage = [UIImage imageNamed:#"tableBG"];
cell.backgroundView = [[UIImageView alloc] initWithImage:bgImage];
// filling them in
if (req.iD.integerValue < 0) {
[cell.histLabel setText:NSLocalizedString(#"New", nil)];
} else {
[cell.histLabel setText:req.iD.stringValue];
}
[cell.datelabel setText:[labelDateFormatter stringFromDate:req.createDate]];
switch (req.status) {
case ReqStatusNew: [cell.imageView setImage:nil];
break;
case ReqStatusSaved: [cell.imageView setImage:[UIImage imageNamed:#"istGespeichertKiste.png"]];
break;
case ReqStatusApproved: [cell.imageView setImage:[UIImage imageNamed:#"genehmigtKiste.png"]];
break;
case ReqStatusInWFF: [cell.imageView setImage:[UIImage imageNamed:#"workflowKiste.png"]];
break;
case ReqStatusNotApproved: [cell.imageView setImage:[UIImage imageNamed:#"abgelehntKiste.png"]];
break;
case ReqStatusOrdered: [cell.imageView setImage:[UIImage imageNamed:#"istBestelltKiste.png"]];
break;
case ReqStatusDelivered: [cell.imageView setImage:[UIImage imageNamed:#"geliefertKiste.png"]];
break;
}
cell.shortTextLabel.marqueeType = MLContinuous;
cell.shortTextLabel.rate = 50;
cell.shortTextLabel.textAlignment = NSTextAlignmentLeft;
if (reqDescript == nil) {
cell.shortTextLabel.text = [shortTexts componentsJoinedByString:#", "];
} else if (shortTexts.count > 0) {
cell.shortTextLabel.text = [NSString stringWithFormat:#"%#: %#", reqDescript, [shortTexts componentsJoinedByString:#", "]];
} else {
cell.shortTextLabel.text = reqDescript;
}
[cell.shortTextLabel setFrame:CGRectMake(56, 35, 168, 18)];
return cell;
}
In viewWillAppear: I just set the buttons in the navigationcontroller and call
[tableview reloadData]
In viewDidLoad: just adding the delegate of the refresh control
The refresh control just calls [tableview reloadData] after updating the recent and notSent arrays from Core Data
A filter button just calls:
- (IBAction)filterPressed:(UIButton *)sender {
sender.selected = !sender.selected;
NSArray *filters = [dvFilterList componentsSeparatedByString:#","];
if ([[NSUserDefaults standardUserDefaults] boolForKey:[filters objectAtIndex:sender.tag]]){
[[NSUserDefaults standardUserDefaults] setBool:NO
forKey:[filters objectAtIndex:sender.tag]];
} else {
[[NSUserDefaults standardUserDefaults] setBool:YES
forKey:[filters objectAtIndex:sender.tag]];
}
[self updateTableViewData];
// only the section with the recent banfs
NSInteger section = [self numberOfSectionsInTableView:self.tableView] - 1;
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section]
withRowAnimation:UITableViewRowAnimationFade];
}
(updateTableViewData is the method that just updates the recent and notSent arrays from Core Data)
You can try this ,
[tableView reloadData];
You can write this in cellForRowAtIndexPath
BANFCell *cell = (BANFCell *)[tableView dequeueReusableCellWithIdentifier:nil
forIndexPath:indexPath];
I finally found the solution by myself.
In my didSelectRowAtIndexPath: method I call performSegueWithIdentifier: and by giving the selected row as sender variable Xcode is somehow saving just the look of the cell in background that can only be deleted by removing the whole view controller from the stack.
Now I just give self as the sender because I don't need the variable.
So I code this:
[self performSegueWithIdentifier:#"goToReq" sender:self];
Instead of this:
[self performSegueWithIdentifier:#"goToReq" sender:[self tableView:tableView
cellForRowAtIndexPath:indexPath]];
I know this is not the answer for the original question, but might help someone else seeing similar problems.
I've encountered similar behavior with buggy code like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (condition)
{
CustomCell1* cell = [tableView dequeueReusableCellWithIdentifier:#"custom1" forIndexPath:indexPath];
// configure cell
// !! note how "return cell;" is missing !!
}
CustomCell2* cell = [tableView dequeueReusableCellWithIdentifier:#"custom2" forIndexPath:indexPath];
// configure cell
return cell;
}
Was fixed by actually returning the special-case cell from the conditional branch.