I have the following code that loads an image from a web service, I want to check if the image is loaded and then populate a button with an image (button is already in place)
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FeedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kTableViewCellIdentifier forIndexPath:indexPath];
if (!cell) {
cell = [[FeedTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kTableViewCellIdentifier];
}
// Configure the cell...
cell.post_time.font = [UIFont fontWithName:#"ProximaNova-Bold" size:12];
cell.post_price.font = [UIFont fontWithName:#"ProximaNova-Semibold" size:13];
cell.post_address.font = [UIFont fontWithName:#"ProximaNova-Regular" size:13];
cell.post_attributes.font = [UIFont fontWithName:#"ProximaNova-Regular" size:11];
House * house = [Posts objectAtIndex:indexPath.row];
//cell.post_image.clipsToBounds = YES;
//cell.post_image.contentMode = UIViewContentModeScaleAspectFill;
//cell.post_image.contentMode = UIViewContentModeScaleAspectFit;
cell.post_time.text = house.house_ts;
// [cell.post_image setImageWithURL:[NSURL URLWithString:house.house_aws_image_id]];
[cell.post_image setImageWithURL:[NSURL URLWithString:house.house_aws_image_id] placeholderImage:[UIImage imageNamed:#"splash_image_gray"]];
cell.post_attributes.text = [self decodeAttributes:house.house_bed_rms :house.house_bath_rms :house.house_house_siz];
[cell.post_playButton addTarget:self action:#selector(showVideo:) forControlEvents:UIControlEventTouchUpInside];
cell.post_share.tag = indexPath.row;
[cell.post_share addTarget:self action:#selector(shareoptions:) forControlEvents:UIControlEventTouchUpInside];
//[cell.post_playButton setImage:[UIImage imageNamed:#"feed_play_but_pink.png"] forState:UIControlStateHighlighted];
//[cell.post_playButton setBackgroundImage:[UIImage imageNamed:#"heart_btn"] forState:UIControlStateNormal];
[cell.post_playButton setTag:indexPath.row];
[cell.post_playButton setBackgroundImage:[UIImage imageNamed:#"feed_play_but"] forState:UIControlStateNormal];
return cell;
}
Any ideas how to achieve this? Thanks in advance.
Assuming your getting your data form the web service as array, you should parse the array and copy it mutable (or parse it as mutable array directly).
Then you can do this:
id imageObject = [[[self dataArray] objectAtIndex:[indexPath row]] objectForKey:#"imageURL"];
if ([imageObject isKindOfClass:[NSString class]]) {
NSURLConnection *connection = [NSURLConnection ...];
// replace image url in array so that you don't start connections too often.
[[[self dataArray] objectAtIndex:[indexPath class]] setObject:connection forKey:#"imageURL"];
// when connection finishes, replace the connection by the instance of UIImage and reload the corresponding cell
}
else if ([imageObject isKindOfClass:[UIImage class]]) {
[[cell imageView] setImage:imageObject];
}
You can use the JWURLConnection class for easy block programming. If you do so, you can easily use this code (which is from one of my apps and works just fine):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
NSMutableDictionary *post = [[self dataSource] objectAtIndex:[indexPath row]];
id memberCloseup = [post objectForKey:#"image"];
if ([memberCloseup isKindOfClass:[NSString class]]) {
JWURLConnection *connection = [JWURLConnection connectionWithGETRequestToURL:[NSURL URLWithString:memberCloseup] delegate:nil startImmediately:NO];
[connection setFinished:^(NSData *data, NSStringEncoding encoding) {
[post setObject:[UIImage imageWithData:data] forKey:#"image"];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}];
[teamMemberMeta setObject:connection forKey:#"so_team_member-image_close"];
[post setObject:connection forKey:#"image"];
[connection start];
}
else if ([memberCloseup isKindOfClass:[UIImage class]]) {
[[cell imageView] setImage:memberCloseup];
}
return cell;
}
EDIT
You would have to move your loading logic to the view controller. Letting the cell loading itself is wrong, because your cells are getting reused, so one cell represents various data objects and / or images. My solution saves a lot of memory and data usage.
Related
here is my two method I need to update my check mark in Core Data when the user check or uncheck the table view. I have tried so many ways but not able to update my data using code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
UIButton *testButton = [[UIButton alloc]initWithFrame:CGRectMake(5, 5, 40, 40)];
[testButton setImage:[UIImage imageNamed:#"oval"] forState:UIControlStateNormal];
[testButton setImage:[UIImage imageNamed:#"tick"] forState:UIControlStateSelected];
[testButton addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:testButton];
[cell setIndentationLevel:1];
[cell setIndentationWidth:45];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// Configure the cell...
NSManagedObject *note = [self.notes objectAtIndex:indexPath.row];
NSDate *date = [note valueForKey:#"mod_time"];
NSString *dateString = [formatter stringFromDate:date];
cell.textLabel.text = [note valueForKey:#"title"];
cell.detailTextLabel.text = dateString;
return cell;
}
This is my button check method. I need to save data in core field
-(void)buttonTouched:(id)sender
{
UIButton *btn = (UIButton *)sender;
if( [[btn imageForState:UIControlStateNormal] isEqual:[UIImage imageNamed:#"oval"]]) {
//[tableView deselectRowAtIndexPath:indexPath animated:YES];
[btn setImage:[UIImage imageNamed:#"tick"] forState:UIControlStateNormal];
} else {
[btn setImage:[UIImage imageNamed:#"oval"] forState:UIControlStateNormal];
}
}
okay do one thing go to your core data create an other attribute with the name check and type should be string
after that go to topmenu bar -> editor -> nsmanageobjectsubclass and create them in project
after that do this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
FilterButton *testButton = [[FilterButton alloc]initWithFrame:CGRectMake(5, 5, 40, 40)];
UIImageView*sideImage=[[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 40, 40)];
// Configure the cell...
Notes*note=(Notes*)[[self fetchedResultsControllerCate] objectAtIndexPath:indexPath];
NSDate *date =note.mod_time;
NSString *dateString = [formatter stringFromDate:date];
if([note.check isEqualToString:#"yes"]){
[sideImage setImage:[UIImage imageNamed:#"tick"]];
}else{
// [testButton setImage:[UIImage imageNamed:#"oval"] forState:UIControlStateNormal];
[sideImage setImage:[UIImage imageNamed:#"oval"]];
// NSLog(#" write no");
}
testButton.myDate=note.mod_time;
cell.textLabel.text = note.title;
cell.detailTextLabel.text = dateString;
[testButton addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[cell setIndentationLevel:1];
[cell setIndentationWidth:45];
[cell.contentView addSubview:sideImage];
[cell.contentView addSubview:testButton];
return cell;
}
in above code i just try to fetch the status from the cordite to show the check box image and after that i update the status in this second method so when user tabbed on check or uncheck we trigger this method and update the column name check in coredata. but yes i am using fetchviewcontroler and its delegate its simple fetch any update directly so in this we don’t need to do much effort
-(void)buttonTouched:(id)sender
{
FilterButton *btn = (FilterButton *)sender;
NSManagedObjectContext *context = [self managedObjectContext];
Notes * NotesUpdateing;
NSFetchRequest *request= [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Notes" inManagedObjectContext:context];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"mod_time==%#",btn.myDate];
// NSLog(#"btn.myDate ..%#",btn.myDate);
[request setEntity:entity];
[request setPredicate:predicate];
NSError *error = nil;
// Below line is giving me error
NSArray *array = [context executeFetchRequest:request error:&error];
if (array != nil) {
NSUInteger count = [array count]; // may be 0 if the object has been deleted.
if(count==0){
NSLog(#"nothing to updates");
}else{
NotesUpdateing = (Notes*)[array objectAtIndex:0];
if ([NotesUpdateing.check isEqualToString:#"no"]) {
NotesUpdateing.check=#"yes";
NSLog(#" write yes");
btn.selected=NO;
}
else
{
NotesUpdateing.check=#"no";
btn.selected=NO;
NSLog(#" write no");
}
}
}
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self.tableView reloadData];
}
after this changes delete you app from simulator or from device and re compile it
When my UITableView loads for the first time, everything in the code below functions correctly. However, if it reloads for whatever reason (refresh, etc.), it starts assigning a cell.bestMatchLabel.text value of #"Best Match" to random cells, rather than only the first one as I specified in the code. Why is calling reload on my table causing the below code to not run correctly?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//load top 3 data
NSDictionary *currentSectionDictionary = _matchCenterArray[indexPath.section];
NSArray *top3ArrayForSection = currentSectionDictionary[#"Top 3"];
// if no results for that item
if (top3ArrayForSection.count-1 < 1) {
// Initialize cell
static NSString *CellIdentifier = #"MatchCenterCell";
EmptyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[EmptyTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// title of the item
cell.textLabel.text = #"No items found, but we'll keep a lookout for you!";
cell.textLabel.font = [UIFont systemFontOfSize:12];
cell.detailTextLabel.text = [NSString stringWithFormat:#""];
[cell.imageView setImage:[UIImage imageNamed:#""]];
return cell;
}
// if results for that item found
else {
// Initialize cell
static NSString *CellIdentifier = #"MatchCenterCell";
MatchCenterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[MatchCenterCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
tableView.separatorColor = [UIColor clearColor];
if (indexPath.row == 0) {
cell.bestMatchLabel.text = #"Best Match";
cell.bestMatchLabel.font = [UIFont systemFontOfSize:12];
cell.bestMatchLabel.textColor = [UIColor colorWithRed:0.18 green:0.541 blue:0.902 alpha:1];
[cell.contentView addSubview:cell.bestMatchLabel];
}
// title of the item
cell.textLabel.text = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Title"];
cell.textLabel.font = [UIFont systemFontOfSize:14];
// price + condition of the item
NSString *price = [NSString stringWithFormat:#"$%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Price"]];
NSString *condition = [NSString stringWithFormat:#"%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Item Condition"]];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# - %#", price, condition];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0.384 green:0.722 blue:0.384 alpha:1];
// Load images using background thread to avoid the laggy tableView
[cell.imageView setImage:[UIImage imageNamed:#"Placeholder.png"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Download or get images here
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Image URL"]]];
// Use main thread to update the view. View changes are always handled through main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Refresh image view here
[cell.imageView setImage:[UIImage imageWithData:imageData]];
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 2.5;
[cell setNeedsLayout];
});
});
return cell;
}
}
That is because table dequeue same cell for multiple indexes as you scroll down, so you need to add else statement in the following code
if (indexPath.row == 0) {
cell.bestMatchLabel.text = #"Best Match";
cell.bestMatchLabel.font = [UIFont systemFontOfSize:12];
cell.bestMatchLabel.textColor = [UIColor colorWithRed:0.18 green:0.541 blue:0.902 alpha:1];
[cell.contentView addSubview:cell.bestMatchLabel];
[cell.bestMatchLabel setHidden:NO];
} else {
[cell.bestMatchLabel setHidden:YES];
}
but a better approach for this case is to use different cell identifier for that row and only add the bestMatchLabel once when first creating the cell
My Parse cloud code sends back JSON to my iOS app with the following structure:
What I want to do is iterate through this and create a new section in the UITableView for every object in this matchCenterArray.
In this instance, there are three objects in the array, each contains a Top 3 NSDictionary whose value is an array of 3 items, each of which is yet another array of properties. As you can see, I want it set up so that each section has 3 cells, one for each of the top 3 items of that respective matchCenterArray object. I then want it to pull the properties of each item and display it in each cell as the texLabel, detailTextLabel, and thumbnail.
I've tried using a for loop as a solution, but this displays the same item in all cells of the array. This is probably because I'm only looping through matchCenterArray objects, but not additionally looping through items in those objects, as can be seen here:
cell.textLabel.text = [[[[_matchCenterArray objectAtIndex:i] objectForKey:#"Top 3"] objectAtIndex:0]objectForKey:#"Title"];
What I was thinking of doing is nesting a for loop within this one, in place of objectAtIndex:0, but sending a message to a for loop doesn't work.
MatchCenterViewController.m:
#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *matchCenter;
#end
#implementation MatchCenterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewCellStyleSubtitle];
self.matchCenter.frame = CGRectMake(0,50,320,self.view.frame.size.height-100);
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
_matchCenterArray = [[NSArray alloc] init];
}
- (void)viewDidAppear:(BOOL)animated
{
self.matchCenterArray = [[NSArray alloc] init];
[PFCloud callFunctionInBackground:#"MatchCenterTest"
withParameters:#{
#"test": #"Hi",
}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[_matchCenter reloadData];
NSLog(#"Result: '%#'", result);
}
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _matchCenterArray.count;
}
//the part where i setup sections and the deleting of said sections
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 21.0f;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 21)];
headerView.backgroundColor = [UIColor lightGrayColor];
// _searchTerm = [[self.matchCenterArray firstObject] objectForKey:#"Search Term"];
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 0, 250, 21)];
// headerLabel.text = [NSString stringWithFormat:#"%#", searchTerm];
// headerLabel.font = [UIFont boldSystemFontOfSize:[UIFont systemFontSize]];
// headerLabel.textColor = [UIColor whiteColor];
headerLabel.backgroundColor = [UIColor lightGrayColor];
[headerView addSubview:headerLabel];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.tag = section + 1000;
button.frame = CGRectMake(300, 2, 17, 17);
[button setImage:[UIImage imageNamed:#"xbutton.png"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(deleteButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:button];
return headerView;
}
- (IBAction)deleteButtonPressed:(UIButton *)sender {
NSLog(#"Search Term: '%#'", _searchTerm);
[PFCloud callFunctionInBackground:#"deleteFromMatchCenter"
withParameters:#{
#"searchTerm": _searchTerm,
}
block:^(NSDictionary *result, NSError *error) {
if (!error) {
NSLog(#"Result: '%#'", result);
}
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Initialize cell
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
for (int i = 0; i<[_matchCenterArray count]; i++) {
// populate dictionary with results
//NSDictionary *matchCenterDictionary= [_matchCenterArray objectAtIndex:indexPath.row];
// title of the item
cell.textLabel.text = [[[[_matchCenterArray objectAtIndex:i] objectForKey:#"Top 3"] objectAtIndex:0]objectForKey:#"Title"];
cell.textLabel.font = [UIFont boldSystemFontOfSize:12];
// price of the item
cell.detailTextLabel.text = [NSString stringWithFormat:#"$%#", [[[[_matchCenterArray objectAtIndex:i] objectForKey:#"Top 3"] objectAtIndex:0]objectForKey:#"Price"]];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];
// image of the item
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[[_matchCenterArray objectAtIndex:i] objectForKey:#"Top 3"] objectAtIndex:0] objectForKey:#"Image URL"]]];
[[cell imageView] setImage:[UIImage imageWithData:imageData]];
//imageView.frame = CGRectMake(45.0,10.0,10,10);
}
return cell;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Forget about the loops, the delegate method already run on a loop where the iteration number is equal to the datasource count.
numberOfSectionsInTableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _matchCenterArray.count;
}
numberOfRowsInSection
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section{
return _matchCenterArray[section][#"Top 3"].count;
}
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// Initialize cell
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
}
// title of the item
cell.textLabel.text = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Title"];
cell.textLabel.font = [UIFont boldSystemFontOfSize:12];
// price of the item
cell.detailTextLabel.text = [NSString stringWithFormat:#"$%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Price"];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];
// image of the item
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Image URL"]]];
[[cell imageView] setImage:[UIImage imageWithData:imageData]];
return cell;
}
Don't harcode your array count, if your json changes, your code will need to change.
In the future you should look into loading the images asynchronously using a library like SDWebImage in order to avoid lags.
cellForRowAtIndexPath: is called once per numberOfRowsInSection:
You need to add numberOfSections as well:
it should look like this:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Initialize cell
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// the following will set the cell attributes for cells in each section
// to customize per section, use indexPath.section to get which section you are at
// title of the item
cell.textLabel.text = [[[[_matchCenterArray objectAtIndex:indexPath.row objectForKey:#"Top 3"] objectAtIndex:0]objectForKey:#"Title"];
cell.textLabel.font = [UIFont boldSystemFontOfSize:12];
// price of the item
cell.detailTextLabel.text = [NSString stringWithFormat:#"$%#", [[[[_matchCenterArray objectAtIndex:indexPath.row] objectForKey:#"Top 3"] objectAtIndex:0]objectForKey:#"Price"]];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];
// image of the item
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[[_matchCenterArray objectAtIndex:indexPath.row] objectForKey:#"Top 3"] objectAtIndex:0] objectForKey:#"Image URL"]]];
[[cell imageView] setImage:[UIImage imageWithData:imageData]];
return cell;
}
Please refer to apple's tableView guide
I have an existing UITableView that lists a number of cafes in the area. The data for each cafe is being pulled from a MySQL database. When a user clicks on a cafe (cell), it brings a user to a detail view. Currently, users can "Favorite" a cafe by clicking on the star image in each cell (this adds the favorited cell to FavoritesTableView). However, I want users to be able to add a cafe to the FavoritesTableView from the DetailView as well (in other words, "favorite" a cafe from the DetailView). Does anyone know how I would implement this?
Right now, I have the star button in place in my DetailView.m (cafe details):
- (IBAction)buttonpressed:(UIButton *)fave {
if (!checked) {
[checkedButton setImage:[UIImage imageNamed:#"checked.png"] forState:UIControlStateNormal];
checked = YES;
}
else if (checked) {
[checkedButton setImage:[UIImage imageNamed:#"unchecked.png"] forState:UIControlStateNormal];
checked = NO;
}
}
ViewController.m (cafes tableview)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *strainTableIdentifier = #"StrainTableCell";
StrainTableCell *cell = (StrainTableCell *)[tableView dequeueReusableCellWithIdentifier:strainTableIdentifier];
if (cell == nil)
cell = [[StrainTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strainTableIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"StrainTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
if (tableView == self.searchDisplayController.searchResultsTableView) {
NSLog(#"Using the search results");
cell.titleLabel.text = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Title"];
cell.descriptionLabel.text = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Description"];
cell.ratingLabel.text = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Rating"];
cell.ailmentLabel.text = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Ailment"];
cell.actionLabel.text = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Action"];
cell.ingestLabel.text = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Ingestion"];
NSLog(#"%#", searchResults);
} else {
NSLog(#"Using the FULL LIST!!");
cell.titleLabel.text = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Title"];
cell.descriptionLabel.text = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Description"];
cell.ratingLabel.text = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Rating"];
cell.ailmentLabel.text = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Ailment"];
cell.actionLabel.text = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Action"];
cell.ingestLabel.text = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Ingestion"];
}
NSMutableDictionary *item = [Strains objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:#"text"];
[item setObject:cell forKey:#"StrainTableCell"];
BOOL checked = [[item objectForKey:#"checked"] boolValue];
NSLog(#"%i",checked);
UIImage *image = (checked) ? [UIImage imageNamed:#"checked.png"] : [UIImage imageNamed:#"unchecked.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame;
[button setBackgroundImage:image forState:UIControlStateNormal];
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;
return cell;
}
- (void)checkButtonTapped:(id)sender event:(id)event
{
NSLog(#"made it here and event is %#",event);
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.StrainTableView];
NSIndexPath * indexPath ;
indexPath = [self.StrainTableView indexPathForRowAtPoint: currentTouchPosition];
NSLog(#"indexpath is below");
NSLog(#"%#",indexPath);
if (indexPath != Nil)
{
NSMutableDictionary *item = [Strains objectAtIndex:indexPath.row];
BOOL isItChecked = [[item objectForKey:#"checked"] boolValue];
NSMutableArray *quickArray = [[NSMutableArray alloc] initWithArray:Strains];
[quickArray replaceObjectAtIndex:indexPath.row withObject:item];
[item setObject:[NSNumber numberWithBool:!isItChecked] forKey:#"checked"];
Strains = [quickArray copy];
[StrainTableView reloadData];
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
StrainDetailViewController *detailViewController = [[StrainDetailViewController alloc] initWithNibName:#"StrainDetailViewController" bundle:nil]; if ([searchResults count]) {
detailViewController.title = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Title"];
detailViewController.strainDetail = [searchResults objectAtIndex:indexPath.row];
} else {
detailViewController.title = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Title"];
detailViewController.strainDetail = [Strains objectAtIndex:indexPath.row];
NSLog(#"%#", Strains);
}
[self.navigationController pushViewController:detailViewController animated:YES];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"strains"] != Nil) {
NSData *dataSave = [[NSUserDefaults standardUserDefaults] objectForKey:#"strains"];
Strains = [NSKeyedUnarchiver unarchiveObjectWithData:dataSave];
}
if (favoritesArray == Nil) {
favoritesArray = [[NSMutableSet alloc] init];
}
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"favorites"] != Nil) {
NSData *dataSave = [[NSUserDefaults standardUserDefaults] objectForKey:#"favorites"];
favoritesArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataSave];
}
I'm just not sure what sort of code I would add to this in order to make the "Checked" button add the selected cell from UITableView to FavoritesTableView.
Hope this made sense. Any ideas?
From a maintainability stand point, the approach I generally take might not be the best. However, from an implementation stand point, it's very easy. I pass a reference to my object (in your case, the object for the table view that was selected) to the details view. From the details view, I can update that object by switching the flag. Since this object is the same object as the table view, updating it will also update the table view (you may have to redraw the table view cell). Last, I update the database from the details view.
Edit
So in your code, it would look like this.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
StrainDetailViewController *detailViewController = [[StrainDetailViewController alloc] initWithNibName:#"StrainDetailViewController" bundle:nil]; if ([searchResults count]) {
detailViewController.title = [[searchResults objectAtIndex:indexPath.row] objectForKey:#"Title"];
//YOU'RE ALREADY DOING IT :)
detailViewController.strainDetail = [searchResults objectAtIndex:indexPath.row];
} else {
detailViewController.title = [[Strains objectAtIndex:indexPath.row] objectForKey:#"Title"];
//YOU'RE ALREADY DOING IT :)
detailViewController.strainDetail = [Strains objectAtIndex:indexPath.row];
NSLog(#"%#", Strains);
}
[self.navigationController pushViewController:detailViewController animated:YES];
}
You're already sending a reference to the details view! This makes things easy. In your details view, whenever someone favorites a strain, set the favorite flag for the strainDetail object. This will also update the strainDetail object in the table view. It works like this because you're passing a reference (also referred to as a pointer) to the detail view. In other words, both of your strainDetail objects, aren't objects but pointers to a memory address. If you update either strainDetail, the the memory address for both strainDetail gets changed. Not the strainDetail pointer itself. Here's an link for further explanation https://softwareengineering.stackexchange.com/questions/17898/whats-a-nice-explanation-for-pointers
So your Details view handler will look something like this
- (IBAction)buttonpressed:(UIButton *)fave {
if (!checked) {
[checkedButton setImage:[UIImage imageNamed:#"checked.png"] forState:UIControlStateNormal];
checked = YES;
}
else if (checked) {
[checkedButton setImage:[UIImage imageNamed:#"unchecked.png"] forState:UIControlStateNormal];
checked = NO;
}
//updates in both the detail view and the table view
BOOL isItChecked = [[self.strainDetail objectForKey:#"checked"] boolValue];
[self.strainDetail setObject:[NSNumber numberWithBool:checked] forKey:#"checked"];
}
I also suggest creating a Strain class to hold all of your strain data. Working with dictionaries is not fun, is error prone, and takes forever to figure out what keys you need.
You can save "favorite" state in a global variable/singleton and retrieve it when needed.
This way, even if your table view is deallocated, you won't lose its cells' state.
- (void)fetchImages {
if (self.profileImages == nil) {
self.profileImages = [[NSMutableDictionary alloc] initWithCapacity:200];
}
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (id tweet in self.timeline) {
TWRequest *fetchUserImageRequest = [[TWRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://api.twitter.com/1/users/profile_image/%#", [tweet valueForKeyPath:#"user.screen_name"]]] parameters:nil requestMethod:TWRequestMethodGET];
[fetchUserImageRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if ([urlResponse statusCode] == 200) {
[self.profileImages setObject:[UIImage imageWithData:responseData] forKey:[tweet valueForKeyPath:#"user.screen_name"]];
NSArray *indexPath = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:[self.timeline indexOfObject:tweet] inSection:0]];
[self.tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationNone];
}
}];
}
});
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"FavoriteCell"];
// configure cell
id tweet = [self.timeline objectAtIndex:[indexPath row]];
UILabel *tweetLabel = (UILabel *)[cell viewWithTag:102];
tweetLabel.text = [tweet objectForKey:#"text"];
UILabel *usernameLabel = (UILabel *)[cell viewWithTag:101];
usernameLabel.text = [tweet valueForKeyPath:#"user.name"];
UIImageView *profileImage = (UIImageView *)[cell viewWithTag:100];
profileImage.image = [self.profileImages objectForKey:[tweet valueForKeyPath:#"user.screen_name"]];
UILabel *dateLabel = (UILabel *)[cell viewWithTag:103];
NSString *labelString = [[tweet objectForKey:#"created_at"] substringToIndex:10];
dateLabel.text = labelString;
return cell;
}
I get the timeline then want to get the profile images for all of users in the timeline. I need to loop through the tweets and get the image. I'm curious how I can determine when all of the images have been fetched then reload the tableview. As of now this isn't happening. The TWRequest is running after the table is reloaded. What am I doing wrong here? Maybe there is a better way to do this?
Thanks a lot.
That's because [TWRequest performRequestWithHandler:] is an async method. Why do you need to reload the entire table? Why not just reload the cell when you get a image (in the end of your handler block).
If you really want to reload the entire table just keep a count of all your finished requests, and reload the table when all is done.
If you want to reload a single cell you can do something like:
dispatch_sync(dispatch_queue_get_main(), ^{
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:0];
}