I am using the the STTwitter API to make an App only twitter Feed. I have successfully output the tweet to the table cell, but now I'm attempting to connect user profile images and I am running in to some problems. I tried implementing the code I found here, but I was getting an error stating "No known class method for selector 'imageWithContentsOfURL:'" so I fixed the problem by replacing UIImage with CIImage. However, now my app is crashing because I'm trying to output a CIImage to an UIImageView. My code and errors are as follows:
Code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString* boldFontName = #"Avenir-Black";
[self styleNavigationBarWithFontName:boldFontName];
self.title = #"Twitter Feed";
self.feedTableView.dataSource = self;
self.feedTableView.delegate = self;
self.feedTableView.backgroundColor = [UIColor whiteColor];
self.feedTableView.separatorColor = [UIColor colorWithWhite:0.9 alpha:0.6];
//self.profileImages = [NSArray arrayWithObjects:#"profile.jpg", #"profile-1.jpg", #"profile-2.jpg", #"profile-3.jpg", nil];
STTwitterAPI *twitter = [STTwitterAPI twitterAPIAppOnlyWithConsumerKey:#"stuff"
consumerSecret:#"stuff"];
[twitter verifyCredentialsWithSuccessBlock:^(NSString *username) {
[twitter getUserTimelineWithScreenName:#"RileyVLloyd"
successBlock:^(NSArray *statuses) {
self.twitterDataSource = [NSMutableArray arrayWithArray:statuses];
for (int i=1; i <= _twitterDataSource.count; i++) {
NSLog(#"%d", i);
NSDictionary *tweetDictionary = self.twitterDataSource[i];
NSString *final = tweetDictionary[#"profile_image_url"];
NSLog(#"%#", final);
}
[self.feedTableView reloadData];
} errorBlock:^(NSError *error) {
NSLog(#"%#", error.debugDescription);
}];
} errorBlock:^(NSError *error) {
NSLog(#"%#", error.debugDescription);
}];
//[self getTimeLine];
}
#pragma mark Table View Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.twitterDataSource.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"FeedCell3" ;
FeedCell3 *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[FeedCell3 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
cell.nameLabel.text = #"RileyVLloyd";
cell.likeCountLabel.text = #"293 likes";
cell.commentCountLabel.text = #"55 comments";
//NSString* profileImageName = self.profileImage[indexPath.row%self.profileImage.count];
cell.profileImageView.image = _profileImage;
NSInteger idx = indexPath.row;
NSDictionary *t = self.twitterDataSource[idx];
cell.updateLabel.text = t[#"text"];
cell.dateLabel.text = #"1 day ago";
return cell;
}
An easier way to accomplish this would be to use the SDWebImage API. The API asynchronously loads the image, so then you won't have to worry about loading due to the main thread being used for loading the images. Also, this API only requires a couple lines of code below to be added to your UITableViewCell method.
NSString *aURL = t[#"user"][#"profile_image_url"];
[cell.profileImageView setImageWithURL:[NSURL URLWithString:aURL]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Your code does crash because you're trying to get a value for the key profile_image_url on username which is a string, hence the exception <__NSCFString ...> is not key value coding-compliant for the key profile_image_url.
Let's assume that what you really want to do here is retrieving the images for each tweet author.
You have to iterate over the statuses and, for each of them, extract the profile_image_url and create a UIImage with it.
Related
Hello everyone I have a problem with my app ... Within my View Controller hp a CollectionView with a custom cell that should return all of the photos in the Camera Roll section of the app pictures of my iphone.
Now I've done all the steps to show the photos in a ImageView in the custom cell and up to now I have no problem ... My problem is that when I start to scroll through photos, uploading photos is very slow and immediately after the app crashes giving me back this error in the log ..
[GatekeeperXPC]
Connection to assetsd was interrupted or assetsd died 25/02/2017 20:
[Generic] Creating an image
format with an unknown type is an error
Can you tell me if I've taken the right way to show pictures in my collection view? Where did I go wrong? because my app crashes?
Thank you all for any help you can give me
This is the code i utilize
- (void)viewDidLoad {
[super viewDidLoad];
self.nameTextField.delegate = self;
self.emailTextField.delegate = self;
self.passwordTextField.delegate = self;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
_collectionView.backgroundColor = [UIColor clearColor];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[_nameTextField becomeFirstResponder];
[self queryImage];
}
-(void)queryImage {
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
PHFetchResult *collection = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:fetchOptions];
if (collection.firstObject != nil ) {
_photoFound = YES;
_assetCollection = collection.firstObject;
} else {
}
_photoAsset = [PHAsset fetchAssetsInAssetCollection:_assetCollection options:nil];
[_collectionView reloadData];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(80,80);
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSUInteger count = 0;
if (_photoAsset != nil) {
count = [_photoAsset count];
}
return count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *reuseIdentifier = #"imageCell";
UPCameraRollCollectionViewCell* cell = [cv dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor redColor];
PHAsset *asset = [_photoAsset objectAtIndex:indexPath.item];
PHImageManager *imageManager = [PHImageManager defaultManager];
[imageManager requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
[cell setThumbnailImage:result];
}];
return cell;
}
Use PHCachingImageManager.
Apple has an example that shows exactly how to do the sort of thing you're after. Collection views are precisely the intended use case.
The issue I am having, concerns trying to display thumbnail images in a list of UITableViewCells. I have 200 thumbnails to display.
My app downloads a zip file of images from my remote server, unzips the contents into the NSDocumentDirectory. After an update, which happens once a week, the app then displays the thumbnails using [UIImage imageWithContentsOfFile:]
Once, I know this has been cached, I display the thumbnail using [UIImage imageNamed:]
My problem is that when I display 200 thumbnails using
[UIImage imageWithContentsOfFile:] on the first display event after an update, the app sometimes freezes after a few minutes, saying that too many image files are open.
ImageIO: CGImageRead_mapData 'open' failed '/Users/cdesign/Library/Application Support/iPhone Simulator/6.1/Applications/B458A3F5-5B21-49CD-B4D8-17E5189678FA/Documents/91.png'
error = 24 (Too many open files)
This never happens once the images are cached in memory.
Then, when I try and click on a UITableViewCell to proceed to my 'DetailController', I get the following error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle (loaded)' with name '9ka-eC-wSa-view-LRp-aI-LGN' and directory 'MainStoryboard.storyboardc''
*** First throw call stack:
(0x1bd0012 0x1473e7e 0x1bcfdeb 0x5d3ef9 0x4987e7 0x498dc8 0x5e728e 0x498ff8 0x499232 0x4994da 0x4b08e5 0x4b09cb 0x4b0c76 0x4b0d71 0x4b189b 0x4b1e93 0x4b1a88 0x80de63 0x7ffb99 0x7ffc14 0x467249 0x4674ed 0xe715b3 0x1b8f376 0x1b8ee06 0x1b76a82 0x1b75f44 0x1b75e1b 0x16ef7e3 0x16ef668 0x3b7ffc 0x2a1d 0x2945)
libc++abi.dylib: terminate called throwing an exception
This is strange, because I do not have a file called 'MainStoryboard.storyboardc'.
My app has no localisation. My project does not even have a 'en.lproj' folder. I only have 'MainStoryboard.storyboard' in the root.
I am not sure if the 2 error are related. Or, if the second error is responsible for the first. This would suggest that there is no issue with my images?
I must say that when I have tested my app, by using a modified 'getImage' method that only returns cached images, the first error disappears but the second error, concerning the 'MainStoryboard.storyboard' does still occur, occasionally, if I leave the app idle for more than a couple of minutes, on the 'ListController' screen.
Both these issues ONLY ever occur on the 'ListController' screen. The app always launches successfully to display the 'home' screen. On the home screen there is a link to the 'ListController' screen.
Assuming, though, that the image display is the problem, is there a way to preload images during application launch to preload the newly updated images from the document folder into the memory cache, so that I never have to use
[UIImage imageWithContentsOfFile:] to display images in the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath?
If, this is possible, do I use NSCache to achieve the image preload?
And, if I use NSCache, do files with the same name overwrite entries in NSCache, or do I have to delete files, with the same name, first?
Sorry, for preempting, the answer to my initial question, but this may save time?
My app contains a storyboard, called MainStoryboard.storyboard
Target device: iPhone 5
Xcode version: 4.6.3
Here is some relevant code:
AppDelegate.m
- (BOOL)hasImageBeenUpdated:(NSString *)postureid{
BOOL result = NO;
YTAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
if (appDelegate.imagesupdated == nil) {
appDelegate.imagesupdated = [[NSMutableArray alloc] init];
}
NSString *imageid = [NSString string];
for (int i = 0; i < appDelegate.imagesupdated.count; i++) {
imageid = [appDelegate.imagesupdated objectAtIndex:i];
if ([imageid isEqualToString: postureid]) {
result = YES;
}
}
return result;
}
- (UIImage *)getImage:(NSString *)postureid{
self.nsLogsOn = YES;
YTAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
UIImage *result = [[UIImage alloc] init];
UIImageView *imageview = [[UIImageView alloc] init];
NSString *imagePath = [NSString stringWithFormat:#"%#.png",postureid];
if (self.nsLogsOn) {
NSLog(#"appDelegate.imagesHaveBeenDownloaded: %i",appDelegate.imagesHaveBeenDownloaded);
}
if(!appDelegate.imagesHaveBeenDownloaded){
imageview.image = [UIImage imageNamed:imagePath];
if (self.nsLogsOn) {
NSLog(#"image cached no download: %#",imagePath);
}
}
else{
if (appDelegate.imagesupdated == nil) {
appDelegate.imagesupdated = [[NSMutableArray alloc] init];
}
BOOL imageHasBeenUpdated = [self hasImageBeenUpdated:postureid];
if(!imageHasBeenUpdated){
imageview.image = [UIImage imageWithContentsOfFile:[[self documentsFilePath] stringByAppendingPathComponent:imagePath]];
[appDelegate.imagesupdated addObject:postureid];
if (self.nsLogsOn) {
NSLog(#"image download: %#",imagePath);
}
}
else{
imageview.image = [UIImage imageNamed:imagePath];
if (self.nsLogsOn) {
NSLog(#"image cached download: %#",imagePath);
}
}
}
if(imageview.image==nil){
imageview.image = [UIImage imageWithContentsOfFile:[[self documentsFilePath] stringByAppendingPathComponent:imagePath]];
if (self.nsLogsOn) {
NSLog(#"image cache cleared by iOS: %#",imagePath);
}
}
if(imageview.image==nil){
imageview.image = [UIImage imageNamed:#"logo-lotus-imageview-white-c.png"];
if (self.nsLogsOn) {
NSLog(#"image cannot be found: %#",imagePath);
}
}
result = imageview.image;
return result;
}
ListController.m
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [keys count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [posturegroups objectForKey:key];
return [nameSection count];
}
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section {
NSString *key = [keys objectAtIndex:section];
return [key capitalizedString];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = #"plainCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
NSString *key = [keys objectAtIndex:indexPath.section];
NSArray *nameSection = [posturegroups objectForKey:key];
YTPosture *thePosture = [nameSection objectAtIndex:indexPath.row];
YTAppDelegate *appDelegate = [[YTAppDelegate alloc] init];
UIImageView *imageview = (UIImageView *)[cell viewWithTag:3];
imageview.image = [appDelegate getImage:thePosture.postureid];
imageview.backgroundColor = [appDelegate defaultColor];
UILabel *cellLabel1 = (UILabel *)[cell viewWithTag:1];
NSString *aStr = [NSString string];
NSString *bStr = [NSString string];
NSString *title = [NSString string];
if([selection valueForKey:#"symptomExists"]){
aStr = [NSString stringWithFormat:#"%# ",thePosture.memberOrder];
bStr = thePosture.title;
title = [aStr stringByAppendingString:bStr];
}
else{
title = thePosture.title;
}
cellLabel1.text = title;
UILabel *cellLabel2 = (UILabel *)[cell viewWithTag:2];
cellLabel2.text = thePosture.sanskritTransliteration;
YTKeyController *keyController = [[YTKeyController alloc] init];
NSMutableArray *branch = [keyController compileData];
NSString *branchid = thePosture.branchid;
NSString *list = #"1.0,1.0,1.0";
NSArray *listItems = [list componentsSeparatedByString:#","];
for (int i = 0; i < [branch count]; i++) {
YTBranch *theBranch = [branch objectAtIndex:i];
if ([branchid isEqualToString:theBranch.branchid]) {
list = theBranch.key;
listItems = [list componentsSeparatedByString:#","];
break;
}
}
[cell setBackgroundColor:[UIColor colorWithRed:[[listItems objectAtIndex:0] floatValue] green:[[listItems objectAtIndex:1] floatValue] blue:[[listItems objectAtIndex:2] floatValue] alpha:1]];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"Nib name" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIViewController *destination = segue.destinationViewController;
if ([destination respondsToSelector:#selector(setDelegate:)]) {
[destination setValue:self forKey:#"delegate"];
}
if ([destination respondsToSelector:#selector(setSelection:)]) {
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSUInteger section = [indexPath section];
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [posturegroups objectForKey:key];
YTPosture *thePosture = [nameSection objectAtIndex:indexPath.row];
id object = thePosture;
NSDictionary *aselection = [NSDictionary dictionaryWithObjectsAndKeys:
indexPath, #"indexPath",
object, #"object",
nil];
[destination setValue:aselection forKey:#"selection"];
}
}
I have found what the problem was. I feel a little embarrassed!
The first point is that 'MainStoryboard.storyboardc' is meant to be present. The 'c' on the end stands for the compiled version of 'MainStoryboard.storyboard'.
Well, the image issue, was to do with the fact that I was trying to call a method that amongst other things, was compiling an SQLite statement from inside the method below:
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Plus I was also calling another method that was looping through about 200 items from inside the same method.
Effectively, these 2 routines were being run for every UITableViewCell that was scrolling into view.
These two issues were crash and burning the system.
Once I removed these issues I decided to call the images using [UIImage imageWithContentsOfFile], without any problems. In fact the 'ListController' now has about 200 thumbnails in it, and it scrolls as smooth as silk.
I feel like a total idiot for wasting your time. Most definitely, I have learnt a big lesson from this. I need to debug & check more thoroughly, and think about every line of code and the context it is written in. In fact, I have learnt that the only information loaded into the method above, should be data that is already indexed.
Champoul's suggestion to use Instruments in Memory Leaks mode, was how I found out the source of this issue. Thanks very much for your help...
I'm trying to use a global NSMutableDictionary from a dispatch queue. However, the items keep coming back NULL.
What I'm trying to do is access an external json file with a dispatch_queue, then populate a UITableView with this info.
Here's what I have
vc.h:
#interface viewcontroller {
NSMutableDictionary *jsonArray;
}
vc.m:
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //1
#define jsonTest [NSURL URLWithString:#"http://www.sometest.com/test.php"]
-(void)viewDidLoad {
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
jsonTest];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
// if I run the log here, I can access jsonArry and the log prints correctly
NSLog(#"City: %#", [jsonArray objectForKey:#"city"];
});
}
-(NSMutableDictionary *)fetchedData:(NSData *)responseData {
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
return jsonArray;
}
/********************* Table formatting area **********************/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.ipTable) {
if ([ipArray count] == 0){
return 1;
} else { // meta table
return [ipArray count];
}
} else { // IP Meta Data
return [jsonArray count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.myTable) {
NSString *CellIdentifier = NULL;
if ([ipArray count] == 0) {
CellIdentifier = #"No Cells";
} else {
CellIdentifier = #"IP Cell";
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if ([ipArray count] == 0)
{
[cell.textLabel setText:NSLocalizedString(#"None Found", nil)];
return cell;
} else {
IPAddr *theip = [ipArray objectAtIndex: [indexPath row]];
NSString *theipname = [theip ipName];
if ([theipname isEqualToString:#""]) {
[cell.textLabel setText: [theip ipNum]];
[cell.detailTextLabel setText:NSLocalizedString(#"noName", nil)];
} else {
[cell.textLabel setText: [theip ipName]];
[cell.detailTextLabel setText: [theip ipNum]];
}
return cell;
}
} else { // meta table
static NSString *CellIdentifier = #"metaCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// jsonArray content would go here to fill the cells.
/******************** something here to fill the cells using jsonArray ********************/
return cell;
}
} // END UITAbleViewCell
If I access the jsonArray inside the queue, it returns fine and prints the log for the city.
However, if I try to use it outside the queue, it returns NULL.
I'm trying to figure out what is happening, any ideas?
I need to use jsonArray in different methods in the same view, so I need it to be global.
I am fairly sure that the problem is that the data source methods (numberOfRowsInSection,
cellForRowAtIndexPath) are called before the background thread has finished and
filled the jsonArray. Therefore you have to reload the table view when the background
thread has finished:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:jsonTest];
NSError *error;
NSArray *tmpArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_sync(dispatch_get_main_queue(), ^{
// Assign new data to data source and reload the table view:
jsonArray = tmpArray;
[self.metaTableView reloadData];
});
});
}
So the table view would be empty initially, and reloaded later when the data has
arrived.
Try to call the other method(which is using your jsonarray) through nsnotification...I am not sure there might some other ideas/ways of doing this.But i am presenting what i have in my mind.
Put this code inside your fetchedData method,
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(someMethod:) name:#"JSonDownloaded" object: jsonArray];
[[NSNotificationCenter defaultCenter] postNotificationName:#"JSonDownloaded" object: jsonArray];
-(void)someMethod:(NSNotification *)nspk
{
NSLog(#"%#",nspk.object);
//Only after this you can able to access the jsonArray.
}
Don't forget to unregister the observer.
jsonArray is just an instance variable, but not a property. Thus, assigning an object to it does not retain it, and the object may be released as soon as the program returns to the run loop.
I suggest replacing the iVar by #property (strong) NSMutableDictionary *jsonArray; and #synthesize jsonArray;, and assigning the object to it by self.jsonArray = ...
EDIT (see comment of Martin R below):
Thus, if you are not using ARC, assigning an object to it does not retain it, and the object may be released as soon as the program returns to the run loop.
In this case, I suggest replacing the iVar by #property (retain) NSMutableDictionary *jsonArray; and #synthesize jsonArray;, and assigning the object to it by self.jsonArray = ...
Hello I am trying to use EGOImageView inside a CustomTableViewCell who i made to customize the cell. This is the code where I used the EGOImageView.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString* simpleTableIdentifier = #"Albums";
CustomTableCell* cell = (CustomTableCell*)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (!cell)
{
cell = [[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
NSLog(#"Show once or more times");
}
NSDictionary* dictionary = (NSDictionary*)[self.albumCollection objectAtIndex:indexPath.row];
cell.label.text = [dictionary valueForKey:#"name"];
EGOImageView* imageView = [[EGOImageView alloc] initWithPlaceholderImage:[UIImage imageWithContentsOfFile:#""]];
[imageView setImageURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=small&access_token=%#", (NSString*)[dictionary valueForKey:#"id"], [[FBSession activeSession] accessToken]]]];
[imageView setFrame:CGRectMake(0.0f,0.0f,78.0f,78.0f )];
[cell.iView addSubview:imageView];
[imageView release];
The image on each cell loading the same image. Would it be because it reused the cell while loading the image.
I found a problem I can't think of why the problem happened. I used the graph api to grab the image https://graph.facebook.com/%#/picture?type=small&access_token=%# where the first parameter was the album id.
To make myself easy to see the problem I only used one album on the cell, no matter what album i used the same photo turned up. But when I copy the link to the browser, the actual photo url shown on the address bar with the image shown and it shown the correct photos.
Does anyone know what was wrong.
Here is example. It loads user pics from some server in background and updates cell image. Note that imageView.image is set to nil at the beginning. This is dome for the case of cell reuse so that you will have no image rather than wrong image for the time while its downloading.
One more thing to add is that, would be also good to have a cache so that it does not download images all the time. Another nice thing is to not download images in edge networks.
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TransactionCell";
NSMutableArray *data = searching ? searchResult : dataSource;
NSDictionary *object = [data objectAtIndex:[indexPath row]];
UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithIdentifier:CellIdentifier] autorelease];
}
cell.imageView.image = nil;
cell.textLabel.text = #"Your cell text";
NSString *contact = #"foo#gmail.com";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imgData = [appDelegate addUserPic:contact];
if (imgData == nil && netStatus == ReachableViaWiFi) {
NSString *url = [NSString stringWithFormat:#"http://somehost.com/userpic/%#", contact];
imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
}
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *updateCell = [self.tableView cellForRowAtIndexPath:indexPath];
if (updateCell) {
if (imgData) {
[appDelegate setUserPic:contact imgData:imgData];
updateCell.imageView.image = [UIImage imageWithData:imgData];
} else {
updateCell.imageView.image = nil;
}
/* This forces the cell to show image as now
it has normal bounds */
[updateCell setNeedsLayout];
}
});
});
return cell;
}
When the callback block for loadImage is run below, the table cell may have since been reused. So the image is applied to "imageView" is not relevant to this reused cell, it's the image for the old cell.
If I make the identifier unique for each cell that has an image, the problem goes away. But this gives poor performance with many results.
Can I somehow use the same reuse identifier with a callback block and have the images turn up in the correct cells?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *place;
PlaceTableViewCell *cell; // UITableViewCell subclass
NSString *identifier = #"PlaceTableViewCell";
if (cell == nil) {
NSArray *objects;
objects = [[NSBundle mainBundle] loadNibNamed:#"PlaceTableViewCell" owner:self options:nil];
for(id object in objects) {
if([object isKindOfClass:[PlaceTableViewCell class]]) {
cell = (PlaceTableViewCell *)object;
break;
}
}
}
UIImageView *imageView;
if((imageView = (UIImageView*)[cell viewWithTag:1])) {
NSString *filename;
int placeImageId = 0;
place = [places objectAtIndex:indexPath.row];
if(place) {
placeImageId = [[d objectForKey:#"placeImageId"] intValue];
if(placeImageId) {
[[RestAPIConnector sharedInstance] loadImage :placeImageId :#"thumb" :^(NSString *response){
NSDictionary *image = [response JSONValue];
if ([image objectForKey:#"img"]) {
NSString *b64Img = [image objectForKey:#"img"];
UIImage *ui = [UIImage imageWithData:[Base64 decode:b64Img]];
imageView.image = ui;
}
}];
}
}
}
return cell;
}
here is what I'm doing.
instead of using the cell directly, I'm passing in the index path
if(user.profileImage == nil)
{
if (self.tableView.dragging == NO && self.tableView.decelerating == NO) {
NSLog(#"file for user %d doesn't exist", [user.userId intValue]);
[self startUserProfileImageDownload:user forIndexPath:indexPath];
}
}
else
{
cell.profileImageView.image = user.profileImage;
}
once the download is complete, use the index path to retrieve the cell, and update the image
MessageCell *cell = (MessageCell *)[self.tableView cellForRowAtIndexPath:path];
// Display the newly loaded image
cell.profileImageView.image = user.profileImage;
CALayer *roundedLayer = [cell.profileImageView layer];
MessageCell is my custom cell. if you don't have use customer cell, you can use Tag to retrieve the imageView back.
I'd create a dictionary to hold the images, then attempt to read from the dictionary in cellForRowAtIndexPath:
#property(retain)NSMutableDictionary *imageData;
//...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//...
UIImageView *imageView;
if((imageView = (UIImageView*)[cell viewWithTag:1])) {
int placeImageId = 0;
place = [places objectAtIndex:indexPath.row];
if(place) {
placeImageId = [[d objectForKey:#"placeImageId"] intValue];
if(placeImageId) {
NSObject *image = [imageData objectForKey:[NSNumber numberWithInt:placeImageId]];
if ([image isKindOfClass:[UIImage class]) {
imageView.image = (UIImage *)image;
} else if (![image isEqual:#"downloading"]) {
[imageData addObject:#"downloading" forKey:[NSNumber numberWithInt:placeImageId]];
[[RestAPIConnector sharedInstance] loadImage:placeImageId onSuccess:^(NSString *response){
NSDictionary *image = [response JSONValue];
if ([image objectForKey:#"img"]) {
NSString *b64Img = [image objectForKey:#"img"];
[imageData addObject:[UIImage imageWithData:[Base64 decode:b64Img]] forKey:[NSNumber numberWithInt:placeImageId]];
}
}];
}
}
}
}
return cell;
}
Some potential optimizations:
As in #Jun1st's sample, don't load images for cells while scrolling
Add download operations to an NSOperationQueue and prioritize the most recently requested ones first (deprioritizing the ones that have scrolled by)
Save downloaded images to the filesystem and check there first