I am trying to download an image off of a server asynchronously. After the image is downloaded and placed, I then want the app to go to the next view controller with an image view that will contain the image. Here is my code:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://...../.png"]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error ) {
image = [[UIImage alloc] initWithData:data];
if (image != NULL) {
[self performSegueWithIdentifier:#"midnight" sender:self];
}
}
NSLog(#"%#", image);
}];
}
The problem is that the next view controller will come up with nothing on it for about 10-15 seconds and then show the image and the text that is supposed to be displayed on the view controller. Is there something that I am doing wrong here?
This worked fine for me. I used your code, but changed the operation queue to the mainQueue, and added code to pass the image to the ImageViewController:
#import "ViewController.h"
#import "ImageViewController.h"
#interface ViewController ()
#property (strong,nonatomic) UIImage *image;
#end
#implementation ViewController
-(IBAction)downloadPic:(id)sender {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://atmire.com/labs17/bitstream/handle/123456789/7618/earth-map-huge.jpg?sequence=1"]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error ) {
_image = [[UIImage alloc] initWithData:data];
if (_image != NULL) {
NSLog(#"Image id: %#", _image);
NSLog(#"Image size is: %#", NSStringFromCGSize(_image.size));
[self performSegueWithIdentifier:#"midnight" sender:self];
}
}else{
NSLog(#"Error is: %#",error);
}
}];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ImageViewController *ivc = segue.destinationViewController;
ivc.receivedImage = _image;
}
In the ImageViewController, I created a property, receivedImage, and had just this code:
#interface ImageViewController ()
#property (weak,nonatomic) IBOutlet UIImageView *iv;
#end
#implementation ImageViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.iv.image = self.receivedImage;
}
Related
I wrote a category for FBSDKProfile provided by the Facebook SDK V4 for iOS. This category enables me to fetch the user profile image and access it using the [FBSDKProfile currentProfile] singleton instance.
This is my category header file:
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <objc/runtime.h>
static char const * const kProfileImageKey = "profile_image";
#interface FBSDKProfile (ProfileImage)
+(void)fetchProfileImageWithBlock:(void (^)(BOOL succeeded))handler;
#property (nonatomic, strong) UIImage *profileImage;
#end
And here's the implementation file:
#import "FBSDKProfile+ProfileImage.h"
#implementation FBSDKProfile (ProfileImage)
+(void)fetchProfileImageWithBlock:(void (^)(BOOL succeeded))handler {
FBSDKProfile *currentProfile = [FBSDKProfile currentProfile];
NSString *userId = currentProfile.userID;
if (![userId isEqualToString:#""] && userId != Nil)
{
[self downloadFacebookProfileImageWithId:userId completionBlock:^(BOOL succeeded, UIImage *profileImage) {
currentProfile.profileImage = profileImage;
[[NSNotificationCenter defaultCenter] postNotificationName:FBSDKProfileDidFetchProfileImageNotification object:nil];
if (handler) { handler(succeeded); }
}];
} else
{
/* no user id */
if (handler) { handler(NO); }
}
}
+(void)downloadFacebookProfileImageWithId:(NSString *)profileId completionBlock:(void (^)(BOOL succeeded, UIImage *profileImage))completionBlock
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large", profileId]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error)
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES, image);
} else{
completionBlock(NO, nil);
}
}];
}
#pragma mark - custom getter/setter methods
-(void)setProfileImage:(UIImage *)profileImage {
objc_setAssociatedObject(self, kProfileImageKey, profileImage, OBJC_ASSOCIATION_ASSIGN);
}
-(UIImage *)profileImage {
return objc_getAssociatedObject(self, kProfileImageKey);
}
#end
The problem
This solution works just the way it should most of the time, but it does, however, frequently fail. From what I can tell, I think it has to do with the storage of the image.
Upon the exception, if I do po [FBSDKProfile currentProfile].profileImage, it returns:
error: property 'profileImage' not found on object of type 'FBSDKProfile *'
error: 1 errors parsing expression
If I hover the pointer over a [FBSDKProfile currentProfile] instance, it doesn't display the profileImage property in the list of properties.
This is where it failed:
May be this could help you.
-(void)getFacebookProfileInfos:(NSString*)token{
FBSDKGraphRequest *requestMe = [[FBSDKGraphRequest alloc]initWithGraphPath:#"me" parameters:#{#"fields":#"id, name, picture.type(large),email"}];
FBSDKGraphRequestConnection *connection = [[FBSDKGraphRequestConnection alloc] init];
[connection addRequest:requestMe completionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error)
{
if(result)
{
APP_DELEGATE.socialEmail=result[#"email"];
APP_DELEGATE.socialName= result[#"name"];
APP_DELEGATE.socialImage= result[#"picture"][#"data"][#"url"];
APP_DELEGATE.socialAcessToken=token;
HomeVC *obj = SB_IDENTIFIER(#"home");
SB_PUSH(obj);
}
else
{
NSLog(#"%#", [error localizedDescription]);
}
}];
[connection start];
}
I used the following code to test out fetching data from the provided link in the code, but both data and response are nil.
RestViewController.h
#import <UIKit/UIKit.h>
#interface RestViewController : UIViewController
#property (nonatomic, strong) IBOutlet UILabel *greetingId;
#property (nonatomic, strong) IBOutlet UILabel *greetingContent;
- (IBAction)fetchGreeting;
#end
RestViewController.m
#import "RestViewController.h"
#interface RestViewController ()
#end
#implementation RestViewController
- (IBAction)fetchGreeting;
{
NSURL *url = [NSURL URLWithString:#"http://rest-service.guides.spring.io/greeting"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
self.greetingId.text = [[greeting objectForKey:#"id"] stringValue];
self.greetingContent.text = [greeting objectForKey:#"content"];
}
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchGreeting];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
In the storyboard, there are 3 UI components, two text labels and one button. Once the button (fetchGreeting) is clicked, the labels should fetch the data from the link and update the two text labels (greetingID and greetingContent) with the right data.
I followed the instructions from the following link exactly, but it still doesn't seem to fetch any data and can't seem to figure it out. Any guidance would be greatly appreciated.
Your code has a type here:
- (IBAction)fetchGreeting;
Should be:
- (IBAction)fetchGreeting
Also, you're using a deprecated method for IOS9, which is this:
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
self.greetingId.text = [[greeting objectForKey:#"id"] stringValue];
self.greetingContent.text = [greeting objectForKey:#"content"];
}
}];
Also, if you're planning to work with IOS9, you need to either use HTTPS or temporarily allow HTTP connections by adding this to your project's PLIST.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
Here's an updated method:
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data.length > 0 && error == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
self.greetingId.text = [[greeting objectForKey:#"id"] stringValue];
self.greetingContent.text = [greeting objectForKey:#"content"];
}
}];
[downloadTask resume];
I'm trying to return String from this method i have two class
first one is for UI and it have two input text user and pass and also i have submit button , another one only doing the following method .
I'm trying to return string from the other class to this class and show the string in alert .
#import "LoginPage.h"
#implementation LoginPage
-(NSString *)responsData:(NSString *)loginUrl input1:(NSString *)username input2:(NSString *)password
{
NSString *urlAsString = loginUrl;
NSString*test;
inUsername = username;
inPassword = password;
NSURL *url = [NSURL URLWithString:urlAsString];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setTimeoutInterval:30.0f];
[urlRequest setHTTPMethod:#"POST"];
// Setting Username and password
NSString *body = [NSString stringWithFormat:#"sended=yes&username=%#&password=%#",username,password];
[urlRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error)
{
if ([data length] >0 && error == nil){ NSString *html =
[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
// NSLog(#"%#", html);
self.lastValue = [NSString stringWithFormat:#"%#",html];
}
else if ([data length] == 0 && error == nil){
//NSLog(#"Nothing was downloaded.");
self.lastValue = [NSString stringWithFormat:#"No thing was downloaded"];
}
else if (error != nil){
// NSLog(#"Error happened = %#", error);
self.lastValue = [NSString stringWithFormat:#"%#",error];
} }];
NSLog(#"%#",self.lastValue);
return self.lastValue;
}
// Do any additional setup after loading the view, typically from a nib.
#end
i want to use this function in another view ( already i include the header of this file ) but i can't , can any one solve this >
another view
- (IBAction)submit:(id)sender {
LoginPage * login = [[LoginPage alloc]init];
NSString * dataRe;
dataRe = [login responsData:#"http://fahads-macbook-pro.local/ios/post.php" input1:#"admin" input2:#"1234"];
NSLog(#"%#",login.lastValue);
if (dataRe != nil) {
UIAlertView * alert =[[UIAlertView alloc]
initWithTitle:#"Hello Fahad"
message:[NSString stringWithFormat:#"%#",dataRe] delegate:self cancelButtonTitle:#"Okay ! " otherButtonTitles:nil, nil];
[alert show];
}
}
Thank you again
When you call the function on the other view, it send an asynch request to the web.
So when you do:
return self.lastValue;
lastValue is still empty or with the previous value because the competionHandler need still to be called. Code of the completionHandler, is just a peace of code passed to the function, that will be called at right moment. So the function arrive to the end where is your return.
When instead the completion handler block is called (because the request has produced a response), you assign the value:
self.lastValue = [NSString stringWithFormat:#"%#",html];
Now lastValue is right.
So your function shouldn't return an NSString, but should return void.
To pass the string to the other controller, you should use the delegation pattern.
This is a very quickly example
SecondViewController.h
#protocol SecondViewControllerDelegate <NSObject>
- (void)lastValueDidUpdate:(NSString *)lastValue;
#end
#interface SecondViewController : UIViewController
#property (weak, nonatomic) id<SecondViewControllerDelegate>delegate;
#end
SecondViewController.m
#import "SecondViewController.h"
#implementation SecondViewController
-(NSString *)responsData:(NSString *)loginUrl input1:(NSString *)username input2:(NSString *)password
{
NSString *urlAsString = loginUrl;
NSString*test;
inUsername = username;
inPassword = password;
NSURL *url = [NSURL URLWithString:urlAsString];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setTimeoutInterval:30.0f];
[urlRequest setHTTPMethod:#"POST"];
// Setting Username and password
NSString *body = [NSString stringWithFormat:#"sended=yes&username=%#&password=%#",username,password];
[urlRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
__weak typeof (self) weakSelf = self;
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error)
{
if ([data length] >0 && error == nil){ NSString *html =
[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
// NSLog(#"%#", html);
weakSelf.lastValue = [NSString stringWithFormat:#"%#",html];
}
else if ([data length] == 0 && error == nil){
//NSLog(#"Nothing was downloaded.");
weakSelf.lastValue = [NSString stringWithFormat:#"No thing was downloaded"];
}
else if (error != nil){
// NSLog(#"Error happened = %#", error);
weakSelf.lastValue = [NSString stringWithFormat:#"%#",error];
}
[weakSelf.delegate lastValueDidUpdate:weakSelf.lastValue];
}];
}
#end
FirstViewController.h
#import "SecondViewController.h"
#interface FirstViewController : UIViewController <SecondViewControllerDelegate>
#property (strong, nonatomic) SecondViewController *secondViewController;
#end
FirstViewController.m
#import "FirstViewController.h"
#implementation FirstViewController
- (void)viewDidLoad {
//your code
[_secondViewController setDelegate:self];
//your code
}
//your code
#end
Note that i use a weak reference to self because otherwise you can create retain cycle.
Define a method in your first class i.e. UI class like:
- (void)callFromBlock:(NSString*)stringFromResponse
{
if (stringFromResponse != nil) {
UIAlertView * alert =[[UIAlertView alloc]
initWithTitle:#"Hello Fahad"
message:[NSString stringWithFormat:#"%#",stringFromResponse] delegate:self cancelButtonTitle:#"Okay ! " otherButtonTitles:nil, nil];
[alert show];
}
}
and the submit method should look like:
- (IBAction)submit:(id)sender {
LoginPage * login = [[LoginPage alloc]init];
NSString * dataRe;
dataRe = [login responsData:#"http://fahads-macbook-pro.local/ios/post.php" input1:#"admin" input2:#"1234"];
}
Now instead of return statement in the block, call the callFromBlock method from the block when you get the response and pass the string to this method you were trying to return.
Hope it helps.
I am writing an app that displays images from url in a view. The idea is that when the view appears, image is dowloaded and it actualizes a UIImamgeView in the view.
I am using a Asyncrounse method in this way:
-(void)downloadASyncro:(NSString*)urlLink{
NSURL * imageURL = [NSURL URLWithString:urlLink];
[self downloadImageWithURL:imageURL completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
NSLog(#"scaricaImmagineASyncro Succeded= %#",image);
picView.image = image;
}
else {
//default image
picView.image = [UIImage imageNamed:#"icon_old.jpg"];
}
}];
}
the downloadImageWithURL method is:
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
When I call the method:
[self downloadASyncro:link];
and the operation sees end with success (NSLOG), picView.image = image; should actualize the view showing the image downloaded , should not it? But immage does not appear...
Any idea? Thanks in advance.
I am sending an asynchronous http GET request and the completionHandler is being called correctly. Code in the callback like NSLog gets run as I can see the output in the logs. However, the lines: self.imageView.image = nil; doesn't seem to go into effect until a few seconds after the NSLog statement "got here". Does anyone know what's happening? The sample code is below:
In ViewController.m:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#end
#implementation ViewController
#synthesize imageView = _imageView;
-void viewDidLoad {
// ImageView
UIImage *image = [UIImage imageNamed:#"test.jpg"];
self.imageView.backgroundColor = [UIColor blackColor];
self.imageView.clipsToBounds = YES;
self.imageView.image = image;
}
-void test {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://someurl"] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod:#"GET"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
self.imageView.image = nil;
NSLog(#"got here");
}];
}
#end
Ah, looks like setting the queue param to [NSOperationQueue mainQueue] fixed as it is here:
NSURLConnection sendAsynchronousRequest:queue:completionHandler: making multiple requests in a row?
As Undept suggested, call thoses lines on main thread. Like so:
dispatch_async (dispatch_get_main_queue (), ^{
self.imageView2.image = nil;
self.imageView.image = nil;
});