ALAssetsLibrary accessing all photos 1000 + from camera roll app crashes - objective-c

I want to access all photos from camera roll my code is working fine if i have few images in camera roll then app is working fine but if i have more than 1000 photos then app crashes here is my code
ALAssetsLibrary *al = [[ALAssetsLibrary alloc] init];
[al enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
NSMutableArray *arrImages=[[NSMutableArray alloc]init];
NSString *albumName = [group valueForProperty:ALAssetsGroupPropertyName];
if (albumName !=nil) {
[arrGroupsNames addObject:albumName];
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
{
if (asset)
{
UIImage *img=[self imageForAsset:asset];
[arrImages addObject:img];
}
}
];
[arrGroups addObject:arrImages];
}
[self.tableVieww reloadData];
}
failureBlock:^(NSError *error)
{
// User did not allow access to library
NSLog(#"error");
}
];
and here is the function which return image from ALAsset
-(UIImage*) imageForAsset:(ALAsset*) aAsset{
ALAssetRepresentation *rep;
rep = [aAsset defaultRepresentation];
return [UIImage imageWithCGImage:[rep fullResolutionImage]];
}

The issue is that be creating a UIImage for each photo in the users camera roll you're going to run out of memory. This can easily be 1000+ images, and using fullResolutionImage will get the largest version of the image.
To resolve this, instead of composing an array of UIImage's, put the ALAsset in the array. Then create the UIImage from the ALAsset only when it's required on screen.

You are storing all 1000+ UIImage in an array and you are out of memory. I have the same issue, mine was 2000+ images and using the implementation below works for me.
I also suggest, you use [asset thumbnail] when displaying images especially if it's just for UITableViewCell or for other purposes that do not require the full resolution of the image.
Here a solution:
//First you need to declare `ALAssetsLibrary *al` as a global variable in the class
//
//like this:
//
//.h
#interface YOURCLASS : UIViewController
{
ALAssetsLibrary *al;
}
#end
//
//or inside your .m
//
#interface YOURCLASS ()
{
ALAssetsLibrary *al;
}
---
// you need to do this to check if 'al' is already declared and to declare once
if (al == nil)
al = [[ALAssetsLibrary alloc] init];
[al enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
...
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
{
if (asset)
{
/*//your code
UIImage *img=[self imageForAsset:asset];
[arrImages addObject:img];
*/
//instead of storing the actual UIImage object just store the `asset` in your array
//like
[arrImages addObject:asset];
}
}];
...
}
failureBlock:^(NSError *error)
{
// User did not allow access to library
NSLog(#"error");
}];
In your table delegate cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
//you need to get the image using the stored asset
//like:
//cast what's inside the array
ALAsset *asset = (ALAsset *)self.arrGroups[indexPath.row];
//self.arrGroups[indexPath.row];
//if just for example i dont know how you access your datas
cell.imageView.image = [UIImage imageWithCGImage:[asset thumbnail]];
//getting the full resolution of the image
//[UIImage imageWithCGImage:[[asset defaultRepresentation] fullResolutionImage]]
//
//or you can simply use what you already have `[self imageForAsset:asset]`
//cell.imageView.image = [self imageForAsset:asset];
...
}
Hope is is helpful for you, Cheers!

-(void)loadCameraPhotos{
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
ALAssetsLibrary *al = [[ALAssetsLibrary alloc] init];
[al enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
NSMutableArray *arrImages=[[NSMutableArray alloc]init];
NSString *albumName = [group valueForProperty:ALAssetsGroupPropertyName];
if (albumName !=nil) {
[arrGroupsNames addObject:albumName];
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
{
if (asset)
{
// if (arrImages.count<7) {
//UIImage *img=[self imageForAsset:asset];
[arrImages addObject:asset];
//}else
// *stop=YES;
}
}
];
[arrGroups addObject:arrImages];
}
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
[self.tableVieww reloadData];
}
failureBlock:^(NSError *error)
{
// User did not allow access to library
NSLog(#"error");
}
];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"PhotoSelectionTableViewCell";
PhotoSelectionTableViewCell* cell = (PhotoSelectionTableViewCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"PhotoSelectionTableViewCell" owner:nil options:nil];
for(id currentObject in topLevelObjects) {
if ([currentObject isKindOfClass:[UITableViewCell class] ]){
cell = (PhotoSelectionTableViewCell *)currentObject;
break;
}
}
}
NSArray *arrtemp=[arrGroups objectAtIndex:indexPath.section];
cell.lblTitle.text=[arrGroupsNames objectAtIndex:indexPath.section];
cell.lblCount.text=[NSString stringWithFormat:#"%lu",(unsigned long)arrtemp.count];
ALAsset *asset = (ALAsset *)[arrtemp lastObject];
cell.imgViewThumbnail.image=[UIImage imageWithCGImage:[asset thumbnail]];;
return cell;
}

I really doubt this solution.NSCache must be used if you want too many photos loaded asap you can see the detail in this repository https://github.com/johnil/JFImagePickerController

Related

How to pass a Dictionary data from table View to the another View Controller

I am fetching the contacts of iPhone and make their Dictionary Check my code
-(void) fetchContacts
{
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted == YES) {
//keys with fetching properties
NSArray *keys = #[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey];
NSString *containerId = store.defaultContainerIdentifier;
NSPredicate *predicate = [CNContact predicateForContactsInContainerWithIdentifier:containerId];
NSError *error;
NSArray *cnContacts = [store unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:&error];
if (error) {
NSLog(#"error fetching contacts %#", error);
} else {
NSString *phone;
NSString *fullName;
NSString *firstName;
NSString *lastName;
UIImage *profileImage;
NSMutableArray *contactNumbersArray;
for (CNContact *contact in cnContacts) {
// copy data to my custom Contacts class.
firstName = contact.givenName;
lastName = contact.familyName;
if (lastName == nil) {
fullName=[NSString stringWithFormat:#"%#",firstName];
}else if (firstName == nil){
fullName=[NSString stringWithFormat:#"%#",lastName];
}
else{
fullName=[NSString stringWithFormat:#"%# %#",firstName,lastName];
}
UIImage *image = [UIImage imageWithData:contact.imageData];
if (image != nil) {
profileImage = image;
}else{
profileImage = [UIImage imageNamed:#"acc_sett.png "];
}
for (CNLabeledValue *label in contact.phoneNumbers) {
phone = [label.value stringValue];
if ([phone length] > 0) {
[contactNumbersArray addObject:phone];
}
}
NSDictionary* personDict = [[NSDictionary alloc] initWithObjectsAndKeys: fullName,#"fullName",profileImage,#"userImage",phone,#"PhoneNumbers", nil];
[_Contacts addObject:personDict];
NSLog(#"%#",phone);
NSLog(#"%#",fullName);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.contacttableview reloadData];
});
}
}
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [_Contacts count];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary* personDict = [_Contacts objectAtIndex:indexPath.row];
UITableViewCell *cell = nil;
cell = [_contacttableview dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
}
cell.imageView.image = [personDict objectForKey:#"userImage"];
cell.textLabel.text = [personDict objectForKey:#"fullName"];
cell.detailTextLabel.text = [personDict objectForKey:#"phoneNumbers"];
return cell;
}
With the help of this code Dictionary Data is display on the tableview now i want to pass image fullname and phone number of a person on the another view controller on selecting their row.
I have tried many method but not get any result
In YourViewController.h, create a property of Person that you want to pass
#property (nonatomic, assign) NSDictionary *person;
In TableViewController.m, didSelectRowAtIndexPath:
YourViewController *vc = [[YourViewController alloc] init];
vc.person = [_Contacts objectAtIndex:indexPath.row];
[self.navigationController pushViewController:vc animated:YES];
First create any NSDictionary object or Your model class object on OtherViewController where you want to navigate then write code like Below,
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
OtherViewController *VC = [[OtherViewController alloc] init];
VC.objContactModel = [_Contacts objectAtIndex:indexPath.row];
[self.navigationController pushViewController:VC animated:YES];
}
Hope it will work.

How to display video from ALAsset to UICollectionview Cell

I tried this code to add all ALAsset created video into my app and then play it. But the video doesn't show in UICollectionView. How is it possible?
I write this code in View Did Load.
_collectionView.dataSource=self;
_collectionView.delegate=self;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
_allVideos = [[NSMutableArray alloc] init];
ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
[assetLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
if (group)
{
[group setAssetsFilter:[ALAssetsFilter allVideos]];
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
{
if (asset)
{
_dic = [[NSMutableDictionary alloc] init];
ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation];
NSString *uti = [defaultRepresentation UTI];
NSURL *videoURL = [[asset valueForProperty:ALAssetPropertyURLs] valueForKey:uti];
NSString *title = [NSString stringWithFormat:#"video %d", arc4random()%100];
UIImage *image = [self imageFromVideoURL:videoURL];
[_dic setValue:image forKey:#"image"];
[_dic setValue:title forKey:#"name"];
[_dic setValue:videoURL forKey:#"url"];
//[_allVideos addObject:_dic];
[_allVideos addObject:asset];
[_collectionView reloadData];
}
}];
}
}
failureBlock:^(NSError *error)
{
NSLog(#"error enumerating AssetLibrary groups %#\n", error);
}];
}
And One method is
- (UIImage *)imageFromVideoURL:(NSURL*)videoURL
{
// result
UIImage *image = nil;
// AVAssetImageGenerator
AVAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];;
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
// calc midpoint time of video
Float64 durationSeconds = CMTimeGetSeconds([asset duration]);
CMTime midpoint = CMTimeMakeWithSeconds(durationSeconds/2.0, 600);
// get the image from
NSError *error = nil;
CMTime actualTime;
CGImageRef halfWayImage = [imageGenerator copyCGImageAtTime:midpoint actualTime:&actualTime error:&error];
if (halfWayImage != NULL)
{
// CGImage to UIImage
image = [[UIImage alloc] initWithCGImage:halfWayImage];
[_dic setValue:image forKey:#"name"];
NSLog(#"Values of dictionary==>%#", _dic);
NSLog(#"Videos Are:%#",videoURL);
CGImageRelease(halfWayImage);
}
return image;
}
And I wrote this in UICollectionView:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _allVideos.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
NSLog(#"allvideo %#", _allVideos);
ALAsset *alasset = [_allVideos objectAtIndex:indexPath.row];
UIImageView *imageview=[[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 80, 80)];
imageview.image = [UIImage imageWithCGImage:alasset.thumbnail];
[cell.contentView addSubview:imageview];
return cell;
}
Write the code in ViewDidLoad
assets = [[NSMutableArray alloc] init];
_library = [[ALAssetsLibrary alloc] init];
UIImage *viewImage;
[_library writeImageToSavedPhotosAlbum:[viewImage CGImage] orientation:(ALAssetOrientation)[viewImage imageOrientation] completionBlock:^(NSURL *assetURL, NSError *error){
if (error) {
NSLog(#"error");
} else {
NSLog(#"url %#", assetURL);
}
}];
[_library enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop){
if (group != NULL) {
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop){
if ([[result valueForProperty:ALAssetsGroupPropertyName] isEqual:#"VideoMaker"]) {
NSLog(#"asset: %#", result);
[assets addObject:result];
}
}];
}
[self.collectionView reloadData];
//[self.activity stopAnimating];
//[self.activity setHidden:YES];
}
failureBlock:^(NSError *error){
NSLog(#"failure"); }];`} `
In this assets is NSMutableArray and library is the ALAssetLibrary.
In UIcollectionviewcell cell for rowAtIndexpath method
-(VideoCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
VideoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CELL" forIndexPath:indexPath];
ALAsset *asset = [assets objectAtIndex:indexPath.row];
[cell.videoImageView setImage:[UIImage imageWithCGImage:[asset thumbnail]]];
return cell;
}

I have some remote images I'm loading into an NSMutableArray but the array isn't populated by the time I need it

Well after troubles with swipe gesture and my own image gallery slider I decided to go with a premade one.
I'm using iCarousel and it works fine. I've tested using an array that is populated with non remote images.
Inside init with coder I done this:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_additionalGarmentImagesArray = [[NSMutableArray alloc] init];
UIImage *image1 = [UIImage imageNamed:#"asos.png"];
UIImage *image2 = [UIImage imageNamed:#"neck.png"];
UIImage *image3 = [UIImage imageNamed:#"diamind.png"];
[_additionalGarmentImagesArray addObject:image1];
[_additionalGarmentImagesArray addObject:image2];
[_additionalGarmentImagesArray addObject:image3];
}
return self;
}
This works fine. However it's not useful to me as my images are remote images. So I've taken the code I used with my own image gallery but the images don't get downloaded on time.
In my viewDidLoad:
PFQuery *query = [PFQuery queryWithClassName:#"Garments"];
[query whereKey:#"title" equalTo:[self garmentTitle]];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
PFFile *additionalImage1 = [object objectForKey:#"image2"];
PFFile *additionalImage2 = [object objectForKey:#"image3"];
PFFile *additionalImage3 = [object objectForKey:#"image4"];
PFFile *additionalImage4 = [object objectForKey:#"image5"];
PFFile *additionalImage5 = [object objectForKey:#"image6"];
// Add main image to array first, the one passed over from previous controller
[_additionalGarmentImagesArray addObject:[self garmentImage]];
if (additionalImage1) {
PFImageView *pfImageView1 = [[PFImageView alloc] init];
[pfImageView1 setFile:additionalImage1];
[pfImageView1 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
if (additionalImage2) {
PFImageView *pfImageView2 = [[PFImageView alloc] init];
[pfImageView2 setFile:additionalImage2];
[pfImageView2 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
if (additionalImage3) {
PFImageView *pfImageView3 = [[PFImageView alloc] init];
[pfImageView3 setFile:additionalImage3];
[pfImageView3 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
} else {
NSLog(#"empty array");
}
}];
This is where the array of images is needed:
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
return [_additionalGarmentImagesArray count];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 380)];
view = imageView;
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
return view;
}
The viewDidAppear method shows array is populated:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"Images Count: %i", [_additionalGarmentImagesArray count]);
}
Please help me solve this issue with solutions. My images are stored remotely and I need a way to have them ready by the time the controller is loaded so they can be used by iCarousel or maybe have iCarousel wait then refresh itself when the array is populated.
Maybe even some how do something in the previous controller which is a UICollectionView. I could tap the cell then get the images I need from the cells object and populate an array then. The only allow the push/segue to happen once the images are in the array. However I don't like that. It means a user would have to wait after they tap to go to the detail view controller where the iCarousel is.
Would appreciate some solutions if possible. It's been a few hours and I've made little progress.
Thanks for your time
Solution was to reloadData at a time where I did have the array. So the best place to do this was inside the viewDidAppear method.
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 400)];
view = imageView;
//create new view if no view is available for recycling
if (view == nil)
{
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
} else {
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
}
return view;
}
Reload Data:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"Images Count: %i", [_additionalGarmentImagesArray count]);
[[self carousel] reloadData];
}

Dispatch Queue and global NSMutableDictionary - Objective C

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 = ...

UITableViewCell using reuseidentifier giving unwanted result with callback block

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