I am displaying a UIImage view with this code:
NSURL *imageUrl = [[NSURL alloc]initWithString:#"http://..."];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:imageUrl];
UIImage *audioArt =[[UIImage alloc] initWithData:imageData];
UIImageView *artView =[[UIImageView alloc] initWithImage:audioArt];
artView.contentMode = UIViewContentModeScaleAspectFit;
artView.autoresizesSubviews = NO;
artView.frame = viewRef.bounds;
[artView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[artView setBackgroundColor:[UIColor blackColor]];
[viewRef setBackgroundColor:[UIColor blackColor]];
[viewRef addSubview:artView];
Is there an event in Objective C to tell when this UIImage has finished loading or when the UIImageView has finished displaying the image?
Many thanks.
in your .h
#interface YourClass : YourSuperclass<NSURLConnectionDataDelegate>
#property (nonatomic) NSMutableData *imageData;
#property (nonatomic) NSUInteger totalBytes;
#property (nonatomic) NSUInteger receivedBytes;
And somewhere call
NSURL *imageUrl = [[NSURL alloc]initWithString:#"http://..."];
NSURLRequest *request = [NSURLRequest requestWithURL: imageUrl];
NSURLConnection *connection = [NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
And also implement the delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) urlResponse;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:#"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;
[self.imageData = [[NSMutableData alloc] initWithLength:self.totalBytes];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.receivedBytes += data.length;
// Actual progress is self.receivedBytes / self.totalBytes
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
imageView.image = [UIImage imageWithData:self.imageData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//handle error
}
Update:
Since NSMutableData initWithLength: creates a data object whose raw data is filled with zeros, replace initWithLength: with just init and it will works.
UIImage doesn't have any delegate or callback which tells you about its successful loading.
If you are getting the image from any url, you can use NSURL's delegates and notifications to track if you received the image or not.
Also you can achieve it as :
- (void)loadImage:(NSString *)filePath {
[self performSelectorInBackground:#selector(loadImageInBackground:) withObject:filePath];
}
- (void)loadImageInBackground:(NSString *)filePath {
#autoreleasepool{
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
[self performSelectorOnMainThread:#selector(didLoadImageInBackground:) withObject:image waitUntilDone:YES];
}
}
- (void)didLoadImageInBackground:(UIImage *)image {
self.imageView.image = image;
}
Related
I am implementing a CollectionView with URL Connection.
The problem that I have is as follows the collectionView delegates are called before the URL connection is finished. Therefore, Collection View does not get any data to display or how could I reload collection view.
Here is my implementation.
-(id)init{
self = [super init];
if (self){
[self loadFromURL];
}
return self;
}
-(void)loadFromURL{
NSURLRequest *theRequest = [NSURLRequest requestWithURL:
[NSURL URLWithString:#"http://www.appybyte.com/satgitsin/products.php?zip=77072"]];
NSURLConnection *theConnection=[[NSURLConnection alloc]
initWithRequest:theRequest delegate:self];
if(theConnection){
responseData = [[NSMutableData alloc] init];
} else {
NSLog(#"failed");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[responseData setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSString *msg = [NSString stringWithFormat:#"Failed: %#", [error description]];
NSLog(#"%#",msg);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&myError];
self.pElements = [[NSMutableArray alloc] init];
for (NSDictionary *aModuleDict in res){
PMosaicData *aMosaicModule = [[PMosaicData alloc] initWithDictionary:aModuleDict];
[self.pElements addObject:aMosaicModule];
}
}
#pragma mark - UICollectionViewDataSource
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [self.pElements count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"cell";
PMosaicCell *pCell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
PMosaicData *pData=[self.pElements objectAtIndex:indexPath.row];
pCell.pMosaicData = pData;
return pCell;
}
By the way, my collectionView is defined in another class called CustomViewController.h as __weak IBOutlet UICollectionView *_collectionView; How could I able to make it work?
at the end of your connectionDidFinishLoading: call [self.collectionView reloadData].
I would recommend using NSURLConnection's class method sendAsynchronousRequest:queue:options:error: instead of of the delegate pattern since you aren't doing any custom buffering logic.
The final code should look something like this:
-(void)loadFromURL {
NSURL *url = [NSURL URLWithString:#"http://www.appybyte.com/satgitsin/products.php?zip=77072"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
__weak typeof(self) weakSelf = self;
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&myError];
// probably should handle the error here
if (myError) {
}
weakSelf.pElements = [[NSMutableArray alloc] init];
for (NSDictionary *aModuleDict in res){
PMosaicData *aMosaicModule = [[PMosaicData alloc] initWithDictionary:aModuleDict];
[weakSelf.pElements addObject:aMosaicModule];
}
[weakSelf.collectionView reloadData];
}];
}
I'm starting NSURLConnection, parsing XML, initialize array from this XML, and show it in the tableView. In connectionDidFinishLoading I'm trying [self.tableView reloadData, but it doesn't work. This is my code:
my .h file:
#interface catalogViewController : UITableViewController //
#property (nonatomic, strong) NSMutableData *receivedData;
#property (nonatomic,retain) NSArray * titleArr;
#property (nonatomic,retain) NSArray * contentArr;
#property (nonatomic,retain) NSArray * ImageURLArr;
#property (nonatomic,retain) NSArray * dateArr;
#property (nonatomic,retain) NSArray * priceArr;
#property (nonatomic,retain) NSDictionary * xmlDictionary;
#property (nonatomic,retain) NSArray * IDArr;
#property (nonatomic) BOOL * didDataLoaded;
#property (strong, nonatomic) IBOutlet UITableView *myTableView;
#end
My .m file:
#import "catalogViewController.h"
#import "XMLReader.h"
#interface catalogViewController ()
#end
#implementation catalogViewController
- (id)initWithStyle:(UITableViewStyle)style {
self = [super initWithStyle:style];
if (self) { } return self;
}
//-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-=-=-=-CONNECTIONS METHOD START-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-=-=-=-=-
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[_receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[connection release];
[_receivedData release];
NSString *errorString = [[NSString alloc] initWithFormat:#"Connection failed! Error - %# %# %#", [error localizedDescription], [error description], [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]]; NSLog(#"%#",errorString);
[errorString release];
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-GET FULL DATA HERE-=-=-=-=-=-=-=-=--=-=-=-=-=-=-
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *dataString = [[NSString alloc] initWithData:_receivedData encoding:NSUTF8StringEncoding];
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-XMLPARSER PART START-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //
NSString *testXMLString = [NSString stringWithContentsOfURL:myURL usedEncoding:nil error:nil];
// -=-=-=-=-=-=-=-=-=-=Parse the XML into a dictionary-=-=-=-=-=-=-=-=-=-=
NSError *parseError = nil;
_xmlDictionary = [XMLReader dictionaryForXMLString:testXMLString error:&parseError];
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-XMLPARSER PART END-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
_titleArr =[[[_xmlDictionary objectForKey:#"result"] objectForKey:#"name"] valueForKey:#"text"];
_IDArr =[[[_xmlDictionary objectForKey:#"result"] objectForKey:#"id"] valueForKey:#"text"];
_priceArr=[[[_xmlDictionary objectForKey:#"result"] objectForKey:#"price"] valueForKey:#"text"];
_ImageURLArr=[[[_xmlDictionary objectForKey:#"result"] objectForKey:#"img"] valueForKey:#"text"];
[connection release];
[_receivedData release];
[dataString release];
_didDataLoaded=TRUE;
[_myTableView reloadData]; // IBOutlet property
[self.tableView reloadData]; //default
}
//-=-=-=-=-=-=-=-=-=-=-Connection methods END-=-=-=-=-=-=-=-=-=-
- (void)viewDidLoad {
[super viewDidLoad];
_didDataLoaded=FALSE;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-XMLPARSER PART START-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//-=-==-=-=-=-=-=-=-=-=-=-=--=-=START Shit with connection-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=
NSString* params = #"request_params";
NSURL* url = [NSURL URLWithString:#"my URL"];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
[request addValue:#"text/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
request.HTTPMethod = #"POST";
request.HTTPBody = [params dataUsingEncoding:NSUTF8StringEncoding];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) {
NSLog(#"Connecting...");
_receivedData = [[NSMutableData data] retain];
} else {
NSLog(#"Connecting error");
}
}
//-=-==-=-=--=-==-=-=-=-=-=--=-==---=-=--==-=-=-=-=-TableView methods-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=--=-=-=-=-=-=-=
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (_didDataLoaded == FALSE) {
return 1;
}
else return self.titleArr.count;
}
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { return 1; }
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"creatures"];
UIImage *creatureImage = nil;
if (_didDataLoaded == FALSE) {
cell.textLabel.text=#"Downloading...";
cell.detailTextLabel.text= #"downloading...";
} else {
cell.textLabel.text = [self.titleArr objectAtIndex:indexPath.row];
cell.detailTextLabel.text= _IDArr[indexPath.row];
NSString *img = self.ImageURLArr[indexPath.row];
creatureImage =[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:img]]]; cell.imageView.image = creatureImage;
}
return cell;
}
#end
XML downloading - OK, parsing - OK, initialising arrays - OK. But when I start project I have exception on the line NSLog(#"%#", self.titleArr objectAtIndex:indexPath.row]; - says: "Thread 1: EXC_BAD_ACCESS (code 1)
How can I understand it's means that it's trying initialize array when it's not prepare. My problem is i can't delay tableView methods or what? How can i fix it? I'm trying fix itfor some days...
Your titleArr is deallocated from memory and then you are requesting object of it so it is giving you a crash. So allocate memory to array by this way.
_titleArr = [[NSArray alloc] initWithArray:[[[_xmlDictionary objectForKey:#"result"] objectForKey:#"name"] valueForKey:#"text"]];
Eventhough it's not recommended way but the following can provide a solution to you
self.tableView.delegate = nil;
self.tableView.datasource = nil;
and when safely your array was populated with the xml data set back to
self.tableView.delegate = self;
self.tableView.datasource = self;
NSURL *url = [NSURL URLWithString:#"http://i0.kym-cdn.com/entries/icons/original/000/005/545/OpoQQ.jpg?1302279173"];
NSData *data = [NSData dataWithContentsOfURL:url];
imageView.image = [[[UIImage imageWithData:data];
I want to set progress bar while downloading.
To give a more detailed example:
in your .h file do
#interface YourClass : YourSuperclass<NSURLConnectionDataDelegate>
in your .m file do
#property (nonatomic) NSMutableData *imageData;
#property (nonatomic) NSUInteger totalBytes;
#property (nonatomic) NSUInteger receivedBytes;
And somewhere call
NSURL *url = [NSURL URLWithString:#"http://i0.kym-cdn.com/entries/icons/original/000/005/545/OpoQQ.jpg?1302279173"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
And also implement the delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) urlResponse;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:#"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;
self.imageData = [[NSMutableData alloc] initWithCapacity:self.totalBytes];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.receivedBytes += data.length;
// Actual progress is self.receivedBytes / self.totalBytes
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
imageView.image = [UIImage imageWithData:self.imageData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//handle error
}
You can't get progress call backs by using that method.
You need to use an NSURLConnection and NSURLConnectionDataDelegate.
The NSURLConnection then runs asynchronously and will send callbacks to its delegate.
The main ones to look at are...
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
and
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
These are all used for getting the connection to do what you're already doing.
EDIT
Actually, see Marc's answer below. It is correct.
You can use MBProgress Hud class for loading view. You can download only two classes from here :-https://github.com/jdg/MBProgressHUD
After you write this code in that class which you want to load the data
Example :In your viewDidLoad you write this
- (void) viewDidLoad
{
MBProgressHud *spinner = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
spinner.mode = MBProgressHUDModeCustomView;
[spinner setLabelText:#"Loading....."];
[spinner setLabelFont:[UIFont systemFontOfSize:15]];
[spinner show:YES];
[self performSelectorInBackground:#selector(getData) withObject:nil];
}
- (void) getData
{
NSURL *url = [NSURL URLWithString:#"http://i0.kym-cdn.com/entries/icons/original/000/005/545/OpoQQ.jpg?1302279173"];
NSData *data = [NSData dataWithContentsOfURL:url];
imageView.image = [[[UIImage imageWithData:data];
[spinner hide:YES];
[spinner removeFromSuperViewOnHide];
}
I have the following method, which basically loads an array of image data into an array:
-(void)loadImages:(NSMutableArray*)imagesURLS{
//_indexOfLastImageLoaded = 0;
[_loadedImages removeAllObjects];
_loadedImages = [[NSMutableArray alloc]init];;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
for (int i=0; i<imagesURLS.count;i++){
NSLog(#"loading image for main image holder at index %i",i);
NSData *imgData = [NSData dataWithContentsOfURL:[imagesURLS objectAtIndex:i]];
UIImage *img = [UIImage imageWithData:imgData];
[_loadedImages addObject:img];
//_indexOfLastImageLoaded++;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"_loadedImages download COMPLETE");
});
});
}
I want to be able to stop it when, for example, a user moves away from the view controller that these images are being loaded in. What is the best way to do so?
Thanks!
You can't cancel NSData dataWithContentsOfUrl:. The best way to achieve cancelable, asynchronous downloading is by using NSURLConnection and the NSURLConnectionDataDelegate.
You set up an NSMutableData object to accumulate all the data as it comes in in chunks. Then when all the data has arrived, you create your image and use it.
.h
#interface ImageDownloader : NSObject <NSURLConnectionDataDelegate>
#property (strong, nonatomic) NSURLConnection *theConnection;
#property (strong, nonatomic) NSMutableData *buffer;
#end
.m
-(void)startDownload
{
NSURL *imageURL = [NSURL URLWithString: #"http://example.com/largeImage.jpg"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL: imageURL];
_theConnection = [[NSURLConnection alloc] initWithRequest: theRequest delegate: self startImmediately: YES];
}
-(void)cancelDownload
{
// CANCELS DOWNLOAD
// THROW AWAY DATA
[self.theConnection cancel];
self.buffer = nil;
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// INITIALIZE THE DOWNLOAD BUFFER
_buffer = [NSMutableData data];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// APPEND DATA TO BUFFER
[self.buffer appendData: data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// DONE DOWNLOADING
// CREATE IMAGE WITH DATA
UIImage *theImage = [UIImage imageWithData: self.buffer];
}
If you want to be more flexible with cancel request i advice you to use NSOperationQueue instead of pushing all request in a row.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
for (int i=0; i<allImagesCount; i++) {
[queue addOperationWithBlock:^{
// load image
}];
}
// for canceling operations
[queue cancelAllOperations];
In you current code you can also define static field and check in for loop, but the best way will be using SDWebImage - https://github.com/rs/SDWebImage for loading image asynch.
I am beginner in IOS programming. My question is my app fetching data from JSON in my web server, when starting the apps, it is slightly lag and delay due to the fetching process, so i would like to show activity indicator when i connecting to JSON data. How can i do that?
My JSON coding:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *urlAddress = [NSURL URLWithString:#"http://emercallsys.webege.com/RedBoxApp/getEvents.php"];
NSStringEncoding *encoding = NULL;
NSError *error;
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:urlAddress usedEncoding:encoding error:&error];
NSData *jsonData = [jsonreturn dataUsingEncoding:NSUTF32BigEndianStringEncoding];
NSDictionary * dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:&error];
if (dict)
{
eventsDetail = [[dict objectForKey:#"eventsDetail"] retain];
}
[jsonreturn release];
}
use the following code
//add a UIActivityIndicatorView to your nib file and add an outlet to it
[indicator startAnimating];
indicator.hidesWhenStopped = YES;
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//Load the json on another thread
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:urlAddress usedEncoding:encoding error:NULL];
[jsonreturn release];
//When json is loaded stop the indicator
[indicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
});
You can use something like below code:
- (void)fetchData
{
[activityIndicator startAnimating];
NSURL *url = [NSURL URLWithString:strUrl];
NSURLRequest *theRequest = [[NSURLRequest alloc]initWithURL:url];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if(theConnection) {
}
else {
NSLog(#"The Connection is NULL");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
webData = [[NSMutableData data] retain];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[webData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"ERROR with theConenction %#",error);
UIAlertView *connectionAlert = [[UIAlertView alloc] initWithTitle:#"Information !" message:#"Internet / Service Connection Error" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[connectionAlert show];
[connectionAlert release];
[connection release];
[webData release];
return;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
//NSLog(#"Received data :%#",theXML);
//[theXML release];
NSDictionary * dict = [[CJSONDeserializer deserializer] deserializeAsDictionary: webData error:&error];
if (dict) {
eventsDetail = [[dict objectForKey:#"eventsDetail"] retain];
}
[activityIndicator stopAnimating];
}