How to optimize the image in SDWebImage - objective-c

I have a json that contains two images' urls, and I use SDwebimage to load the two images, please look at following code
NSDictionary *dic = [_arrayDB objectAtIndex:[indexPath row]];
NSURL *imageUrl = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",imageURL, [dic objectForKey:#"oneimageurl"]]];
__block UIActivityIndicatorView *activityIndicator;
__weak UIImageView *weakImageView = cell.oneimage;
[cell.oneimage sd_setImageWithURL:imageUrl placeholderImage:nil options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize){
if (!activityIndicator) {
[weakImageView addSubview:activityIndicator = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]];
activityIndicator.center = weakImageView.center;
[activityIndicator startAnimating];
}
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}];
NSURL *imageUrl1 = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",imageURL, [dic objectForKey:#"twoimageurl"]]];
__block UIActivityIndicatorView *activityIndicator1;
__weak UIImageView *weakImageView1 = cell.twoimage;
[cell.twoimage sd_setImageWithURL:imageUrl1 placeholderImage:[UIImage imageNamed:#"avatar"] options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize1, NSInteger expectedSize1) {
if (!activityIndicator1) {
[weakImageView1 addSubview:activityIndicator1 = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]];
activityIndicator1.center = weakImageView1.center;
[activityIndicator1 startAnimating];
}
} completed:^(UIImage *image1, NSError *error1, SDImageCacheType cacheType1, NSURL *imageURL1) {
[activityIndicator1 removeFromSuperview];
activityIndicator1 = nil;
}];
this code runs well, but i think it's so more code, and I want to optimize this code, so can you help me? Many thanks.

I think SDwebImage not do resizing - you may use this - https://github.com/mustangostang/UIImage-ResizeMagick
you can use easily like -
[cell.twoimage sd_setImageWithURL:imageUrl1 placeholderImage:[UIImage imageNamed:#"avatar"]];
for image2 -
[cell.twoimage sd_setImageWithURL:imageUrl2 placeholderImage:[UIImage imageNamed:#"avatar"]];
just put these line of code in between for speedy download as -
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[cell.twoimage1 sd_setImageWithURL:imageUrl1 placeholderImage:[UIImage imageNamed:#"avatar"]];
[cell.twoimage2 sd_setImageWithURL:imageUrl2 placeholderImage:[UIImage imageNamed:#"avatar"]];
});

Since you have two UIImageView's you need to populate with a unique UIImage you will have to use separate code for each UIImageView

Related

Creating video preview on iCarousel using PFFiles from Parse IOS

I have created an iCarousel View and am trying to display a video preview in each "cell"/view.
I have the videos stored in Parse and am trying to
Query from the cloud
Retrieve the data from the PFFile
Convert the data to URL
Play URL using AVPlayer
Here my code so far.
-(void)getPast{
dataArray = [[NSMutableArray alloc]init];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
PFQuery *query = [PFQuery queryWithClassName:#"History"];
[query whereKey:#"Location" containsString:[defaults objectForKey:#"location"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
pastArray = [NSMutableArray arrayWithArray:objects];
for (PFObject *files in objects){
PFFile *file = [files objectForKey:#"File"];
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[dataArray addObject:data];
}];
}
[self.carouselView reloadData];
}];
}
Im getting an error saying that my dataArray is empty,
I think the problem here could be that since I'm querying in the background, the For loop is finishing before I have received the data and therefore the array is empty, although I could be wrong and I don't know how to fix this even if I was right.
Code for displaying preview
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view {
PFObject *object = [pastArray objectAtIndex:index];
NSData *data = [dataArray objectAtIndex:index];
NSString *dataString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSURL *URL = [NSURL URLWithString:dataString];
NSLog(#"URL %#",URL);
view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 275)];
self.playerViewController = [[AVPlayerViewController alloc]init];
self.playerViewController.player = [AVPlayer playerWithURL:URL];
self.playerViewController.view.frame = view.bounds;
self.playerViewController.showsPlaybackControls = NO;
[view addSubview:self.playerViewController.view];
view.autoresizesSubviews = YES;
self.playerViewController.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerViewController.player.currentItem];
[self.playerViewController.player play];
return view;
}
How can I fix my code so that each view autoplays the PFFile video corresponding to its index in the Array.
My problems:
Array is empty
Playing content for each view isn't working
Ps. Im aware that I'm not using PFObject *object.
As you guessed the for cycle finishes it's execution way before the blocks are called, you have to make sure the data is loaded before you call reloadData
The first thing that comes to my mind on how to handle this will be something like
for (PFObject *files in objects){
PFFile *file = [files objectForKey:#"File"];
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[dataArray addObject:data];
[self checkData];
}];
}
- (void)checkData {
//Check the if the data is completed
if(dataArray.count == numberOfFiles) { //Maybe a more complex if is required here but you get the idea
//All files are downloaded
dispatch_async(dispatch_get_main_queue(), ^{
//We are sure the data is ready so we reload it
[self.carouselView reloadData];
});
}
}
Also you should always check if NSData is valid before loading it

NSURLSessionDownloadTask Delegates Not Firing

I'm playing around with a tutorial for NSURLSession. I can successfully download an image, however the delegates for download progress and download finished are not triggering. Here is the code :
- (void)viewDidLoad
{
[super viewDidLoad];
NSString * imageUrl = #"http://ichef.bbci.co.uk/naturelibrary/images/ic/credit/640x395/r/ro/rock_pigeon/rock_pigeon_1.jpg";
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
//Download image.
NSURLSessionDownloadTask * getImageTask = [session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"Error sadly for you is %#", [error localizedDescription]);
}
UIImage * downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
dispatch_async(dispatch_get_main_queue(), ^ {
self.imageView.image = downloadedImage;
});
}];
[getImageTask resume];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSLog(#"Temporary File :%#\n", location);
NSError *err = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSURL *docsDirURL = [NSURL fileURLWithPath:[docsDir stringByAppendingPathComponent:#"out1.zip"]];
if ([fileManager moveItemAtURL:location
toURL:docsDirURL
error: &err])
{
NSLog(#"File is saved to =%#",docsDir);
}
else
{
NSLog(#"failed to move: %#",[err userInfo]);
}
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//You can get progress here
NSLog(#"Received: %lld bytes (Downloaded: %lld bytes) Expected: %lld bytes.\n",
bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
And in the .h file :
#import <UIKit/UIKit.h>
#interface SGGViewController : UIViewController <NSURLSessionDelegate> {
IBOutlet UIImageView * imageView;
}
#property (nonatomic, strong) IBOutlet UIImageView * imageView;
#end
Can anyone suggest how to fix ?
Use NSUrlRequest Now delegates will call . Hope this will work
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSessionDownloadTask *downloadTask =nil;
NSString * imageUrl = #"http://fc05.deviantart.net/fs71/i/2012/180/8/f/ios_6_logo_psd___png_by_theintenseplayer-d55eje9.png";
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]] ;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]];
downloadTask = [session downloadTaskWithRequest:request];
[downloadTask resume ];
/*
NSURLSessionDownloadTask * getImageTask = [session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"Error sadly for you is %#", [error localizedDescription]);
}
UIImage * downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
dispatch_async(dispatch_get_main_queue(), ^ {
//self.imageView.image = downloadedImage;
});
}];
[getImageTask resume];
*/
// Do any additional setup after loading the view, typically from a nib.
}
You already have a delegate, so you may as well skip the the completionHandler/block form of task creation, and go all-in on the delegate.
A quick glance at the holy script didn't tell me anything authoritative about whether specifying a completion handler will prevent the delegate methods from being fired, but there is a lot about that relationship that seems mutually exclusive.
If you haven't already, I'd say you should add –URLSession:task:didCompleteWithError: to your delegate. It might capture problems the purely download delegate methods might miss.

How to open view controller after data has been loaded into model object?

How can I check if the NSData dataWithContentsOfURLparsing in my secondary thread are finished? When every image is finished I want to open my view controller. Not before. Now I can open my view controller directly, and sometimes if I'm to quick my table view has no images, because they're not finished yet. Any ideas?
The following code happens in didFinishLaunchingWithOptions in AppDelegate. Im using the SBJSON framework for parsing.
(Im using the storyboard in this project so there's no code for opening the first view controller)
Code:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"json_template" ofType:#"json"];
NSString *contents = [NSString stringWithContentsOfFile: filePath encoding: NSUTF8StringEncoding error: nil];
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *json = [jsonParser objectWithString: contents];
tabs = [[NSMutableArray alloc] init];
jsonParser = nil;
//parsing json into model objects
for (NSString *tab in json)
{
Tab *tabObj = [[Tab alloc] init];
tabObj.title = tab;
NSDictionary *categoryDict = [[json valueForKey: tabObj.title] objectAtIndex: 0];
for (NSString *key in categoryDict)
{
Category *catObj = [[Category alloc] init];
catObj.name = key;
NSArray *items = [categoryDict objectForKey:key];
for (NSDictionary *dict in items)
{
Item *item = [[Item alloc] init];
item.title = [dict objectForKey: #"title"];
item.desc = [dict objectForKey: #"description"];
item.url = [dict objectForKey: #"url"];
if([dict objectForKey: #"image"] != [NSNull null])
{
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^(void)
{
NSURL *imgUrl = [NSURL URLWithString: [dict objectForKey: #"image"]];
NSData *imageData = [NSData dataWithContentsOfURL: imgUrl];
dispatch_async( dispatch_get_main_queue(), ^(void)
{
item.image = [UIImage imageWithData: imageData];
});
});
}
else
{
UIImage *image = [UIImage imageNamed: #"standard3.png"];
item.image = image;
}
[catObj.items addObject: item];
}
[tabObj.categories addObject: catObj];
}
[tabs addObject: tabObj];
}
//sort array
[tabs sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
Tab *r1 = (Tab*) obj1;
Tab *r2 = (Tab*) obj2;
return [r1.title caseInsensitiveCompare: r2.title];
}];
/***** END PARSING JSON *****/
[[UINavigationBar appearance] setTitleTextAttributes: #{
UITextAttributeTextShadowOffset: [NSValue valueWithUIOffset:UIOffsetMake(0.0f, 0.0f)],
UITextAttributeFont: [UIFont fontWithName:#"GreatLakesNF" size:20.0f]
}];
UIImage *navBackgroundImage = [UIImage imageNamed:#"navbar.png"];
[[UINavigationBar appearance] setBackgroundImage:navBackgroundImage forBarMetrics:UIBarMetricsDefault];
UIImage *backButtonImage = [[UIImage imageNamed:#"backBtn.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
UIImage *backButtonSelectedImage = [[UIImage imageNamed:#"backBtn_selected.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonSelectedImage forState: UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
return YES;
Also, if this way of parsing is bad, please tell me!
First of all, you shouldn't use such way of downloading any content from remote host.
There are lots of libraries like AFNetworking, ASIHTTPRequest
which work around CFNetwork or NSURLConnection to handle such things as redirects, error handling etc.
So you should definitely move to one of those (or implement your own based on NSURLConnection).
As a direct answer to your question:
You should use some kind of identifier for counting downloaded images (i.e. for-loop iteration counter) and pass it via +[UINotificationCenter defaultCenter] as a parameter of some custom notification.
Example (assuming that you are blocking current thread by +[NSData dataWithContentsOfURL:]):
for (int i = 0; i < 10; i++) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"someCustomNotificationClassName" object:nil userInfo:#{ #"counter" : #(i) }];
}
More expanded example of NSNotification-based approach:
- (id)init {
self = [super init];
if (self) {
// subscribing for notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDataDownload:) name:#"someCustomNotificationClassName" object:nil];
}
return self;
}
- (void)dealloc {
// unsubscribing from notification on -dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - downloading delegation
- (void)handleDataDownload:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
int counter = [userInfo[#"counter"] intValue];
if (counter == 10) {
// do some work afterwards
// assuming that last item was downloaded
}
}
Also you can use callback technique to manage handling of download state:
void (^callback)(id result, int identifier) = ^(id result, int identifier) {
if (identifier == 10) {
// do some work afterwards
}
};
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions), ^{
// some downloading stuff which blocks thread
id data = nil;
callback(data, i);
});
}

Loading Images from JSON to Xcode

I having a bit difficulty loading images from a json file into UIImage - Table Cells in Xcode. I tried to load the images from the server into a NSArray then populating the table view UIImage cells. Is there something that I am missing here?
Image are located on a SQL server.
Thanks for the help.
Here is the server output from the PHP into Xcode. (cover_image)
(
"13497074790148.jpeg",
"13494650900147.png",
"13494606630147.png",
"13494605220147.jpeg",
"13494602920147.jpeg",
"13494601850147.jpeg",
"13491916300147.jpeg"
)
Here is the code in Xcode
NSArray *itemsimages = [[NSArray alloc]initWithArray:[results valueForKeyPath:#"cover_image"]];
self.itemImages = itemsimages;
Here is the code in table cells
UIImage *imageitm = [UIImage imageNamed: [self.itemImages objectAtIndex: [indexPath row]]];
cell.itmImage.image = imageitm;
return cell;
You don't have those images stored locally so it doesn't have any images to display. I suggest using SDWebImage to provide asyncronous image loading from remote location + caching mechanism.
-(void) viewDidLoad
{
NSURL *url = [NSURL URLWithString:#"YOUR URL"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error;
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSMutableArray *img = [[NSMutableArray alloc]init];
NSArray *websiteDetails = (NSArray *) [json objectForKey:#"logos"];
for(int count=0; count<[websiteDetails count]; count++)
{
NSDictionary *websiteInfo = (NSDictionary *) [websiteDetails objectAtIndex:count];
imagefile = (NSString *) [websiteInfo objectForKey:#"image_file"];
if([imagefile length]>0)
{
NSLog(#"Imagefile URL is: %#",imagefile);
[img addObject:imagefile];
}
}
//NSarray listofURL
listofURL = img;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSURL *url = [NSURL URLWithString:[listofURL objectAtIndex:indexPath.row]];
NSData *image = [[NSData alloc] initWithContentsOfURL:url];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:image];
});
});
}
return cell;
}
You need to have a proper url in json response or you can store the common part of the url in the code itself and append it later with the image name returned from server.
I did as follows in the same condition
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSDictionary *dict = ((NSDictionary *) result)[#"result"];
NSString *url = dict[#"imageURL"];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
[_buttonImageView setImage:image forState:UIControlStateNormal];
where data is the response returned from server.

Background thread returning result of UIImage as nil

This only happens when the Entry has been created on this run of the app. If the Entry is previously created, it fetches the image fine.
This code works fine without using background threads, so it leads me to believe it to be part of the problem. Here's the code I have:
NSMutableDictionary *thumbnails = [[NSMutableDictionary alloc] init];
dispatch_queue_t thumbnailSetupQueue = dispatch_queue_create("com.App.SetupTimelineThumbnails", NULL);
dispatch_async(cellSetupQueue, ^{
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coordinator = [NSManagedObjectContext contextForCurrentThread].persistentStoreCoordinator;
[newMoc setPersistentStoreCoordinator:coordinator];
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
Media *media = [localEntry.media anyObject];
UIImage *image = [media getThumbnail];
NSLog(#"image: %#", image);
[[NSNotificationCenter defaultCenter] removeObserver:self];
});
dispatch_release(cellSetupQueue);
Then
-(UIImage *)getThumbnail {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithFormat:#"%#-%#.jpg",
self.mediaID,
THUMBNAIL_FILENAME]];
UIImage *thumbnail = [UIImage imageWithContentsOfFile:fullPath];
NSLog(#"correct size thumbnail: %#", correctSizeThumbnail);
return correctSizeThumbnail;
}
The NSLog in getThumbnailWithSave returns as a UIImage, the other NSLog returns as nil.
I had this problem explained to be a long time ago and I think this is how I fixed it.
Calling getThumbnail needs to be called back on the main thread.
So adding something such as:
UIImage *image;
dispatch_async(dispatch_get_main_queue(), ^{
image = [media getThumbnail];
});
or
UIImage *image = [media performSelectorOnMainThread:#selector(getThumbnail) withObject: nil, waitUntilDone:NO];
Again this is off the top of my head but I'm pretty sure this is how I went about it.