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];
}
Related
Hello I would like to know how it's possible to have responseString and responseObject with the new version of AFNetworking.
When I made GET operation I have success response with NSURLSessionDataTask and id responseData.
And I would like to have responseString and responseObject.
Thanks for your help.
there is my code not the full code but it's like that
void(^wsFailure)(NSURLSessionDataTask *, NSError *) = ^(NSURLSessionDataTask *failedOperation, NSError *error) {
NSLog(#"failed %#",failedOperation);
[self failedWithOperation:failedOperation error:error];
};
void (^wsSuccess)(NSURLSessionDataTask *, id) = ^(NSURLSessionDataTask * _Nonnull succeedOperation, id _Nullable responseObject) {
NSLog(#"responseData: %#", responseObject);
NSString *str = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"responseData: %#", str);
}}
AFHTTPResponseSerializer *responseSerializer = [self responseSerializerFromResponseType];
AFHTTPRequestSerializer *requestSerializer = [self requestSerializerFromRequestType];
operationManager.requestSerializer = requestSerializer;
operationManager.responseSerializer = responseSerializer;
- (AFHTTPResponseSerializer *)responseSerializerFromResponseType{
if ([self.request.parameters[#"responseType"] isEqualToString:#"xml"]) {
return [AFXMLParserResponseSerializer serializer];
}
else if ([self.request.parameters[#"responseType"] isEqualToString:#"html"]) {
return [AFHTTPResponseSerializer serializer];
}}
Quickly done, I implemented my own ResponseSerializer, which is just a way to encapsulate a AFNetworkingSerializer (~AFHTTPResponseSerializer which is the superclass of the other ones, and respects the AFURLResponseSerialization protocol) which will return a custom serialized object, which will have the 2 properties you want in addition to the NSDictionary/NSArray serialized object: a NSData and a NSString.
.h
#interface CustomResponseSerializer : NSObject <AFURLResponseSerialization>
-(id)initWithResponseSerializer:(id<AFURLResponseSerialization>)serializer;
#end
.m
#interface CustomResponseSerializer()
#property (nonatomic, strong) id<AFURLResponseSerialization> serializer;
#end
#implementation CustomResponseSerializer
-(id)initWithResponseSerializer:(id<AFURLResponseSerialization>)serializer {
self = [super init];
if (self)
{
_serializer = serializer;
}
return self;
}
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing * _Nullable)error {
id serialized = nil;
if ([_serializer respondsToSelector:#selector(responseObjectForResponse:data:error:)]) {
NSError *serializationError = nil;
serialized = [_serializer responseObjectForResponse:response data:data error:&serializationError];
}
//You could put NSError *serializationError = nil; before, and set it into the `CustomSerializedObject` `error` property, I didn't check more about AFNetworking and how they handle a parsing error
return [[CustomSerializedObject alloc] initWithData:data
string:[[NSString alloc] initWithData:data encoding: NSUTF8StringEncoding]
object:serialized];
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:self.serializer forKey:NSStringFromSelector(#selector(serializer))];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
self = [self init];
if (!self) {
return nil;
}
self.serializer = [coder decodeObjectForKey:NSStringFromSelector(#selector(serializer))];
return self;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
CustomResponseSerializer *serializer = [[CustomResponseSerializer allocWithZone:zone] init];
serializer.serializer = [self.serializer copyWithZone:zone];
return serializer;
}
#end
And the object:
#interface CustomSerializedObject: NSObject
#property (nonatomic, strong) NSData *rawData;
#property (nonatomic, strong) NSString *string;
#property (nonatomic, strong) id object;
#property (nonatomic, strong) NSError *error; //If needed
-(id)initWithData:(NSData *)data string:(NSString *)string object:(id)object;
#end
#implementation CustomSerializedObject
-(id)initWithData:(NSData *)data string:(NSString *)string object:(id)object {
self = [super init];
if (self)
{
_rawData = data;
_string = string;
_object = object;
}
return self;
}
#end
How to use:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"https://httpbin.org/get"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
CustomResponseSerializer *responseSerializer = [[CustomResponseSerializer alloc] initWithResponseSerializer:[AFJSONResponseSerializer serializer]];
[manager setResponseSerializer: responseSerializer];
NSURLSessionDataTask *task = [manager dataTaskWithRequest:request
uploadProgress:nil
downloadProgress:nil
completionHandler:^(NSURLResponse * _Nonnull response, CustomSerializedObject * _Nullable responseObject, NSError * _Nullable error) {
NSLog(#"Response: %#", response);
NSLog(#"ResponseObject data: %#", responseObject.rawData); //If you want hex string ouptut see https://stackoverflow.com/questions/1305225/best-way-to-serialize-an-nsdata-into-a-hexadeximal-string
NSLog(#"ResponseObject str: %#", responseObject.string);
NSLog(#"ResponseObject object: %#", responseObject.object);
NSLog(#"error: %#", error);
}];
[task resume];
I have a client that runs their search functionality on their website through cloudsearch. I have been going through the documentation for days, and haven't been able to make a successful search request. I created an NSMutableRequest object, and am running that request through the AWSSignature method [signature interceptRequest:request]; but my task.result is coming back (null).
Here is my code:
AWSTask *task = [signature interceptRequest:request];
[task continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) {
NSLog(#"task.result fromSearch:%#", task.result);
NSData *responseData = task.result;
NSString* newStr = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"newStr:%#", newStr);
NSLog(#"task.error:%#", task.error);
return nil;
}];
Am I on the right track, or is there a better way to do this through the aws iOS sdk?
To put a little more flesh on the bones of Robert's comment, I did it with some help from AFNetworking like so:
#import <AWSCore/AWSSignature.h>
#import <AWSCore/AWSService.h>
#import <AWSCore/AWSCategory.h>
#import <AWSCore/AWSCredentialsProvider.h>
#import <AWSCore/AWSTask.h>
#import "AFNetworking.h"
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = [[NSOperationQueue alloc] init];
}
- (void)performSearch {
AWSAnonymousCredentialsProvider* credentialsProvider = [[AWSAnonymousCredentialsProvider alloc] init];
NSString* searchHost = #"<CloudSearchEndPoint>.eu-west-1.cloudsearch.amazonaws.com";
NSString* query = [self.searchTerms aws_stringWithURLEncoding];
NSURL* searchURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://%#/2013-01-01/search?q=%#", searchHost, query]];
AWSEndpoint* endpoint = [[AWSEndpoint alloc] initWithURL:searchURL];
AWSSignatureV4Signer* signer = [[AWSSignatureV4Signer alloc] initWithCredentialsProvider:credentialsProvider endpoint:endpoint];
NSMutableURLRequest* mutableRequest = [[NSMutableURLRequest alloc] initWithURL:searchURL];
AWSTask* task = [signer interceptRequest:mutableRequest];
[task continueWithBlock:^id(AWSTask* _Nonnull t) {
if (t.error) {
NSLog(#"Error: %#", t.error);
} else if (t.completed) {
NSLog(#"Result is %#", t.result);
}
AFJSONRequestOperation* operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:mutableRequest success:^(NSURLRequest* request, NSHTTPURLResponse* response, id JSON) {
NSLog(#"Success fetching results!");
if (JSON) {
NSDictionary* hitsContainer = [JSON objectForKey:#"hits"];
NSArray* hits = [hitsContainer objectForKey:#"hit"];
NSMutableArray* allResults = [[NSMutableArray alloc] initWithCapacity:hits.count];
for (NSDictionary* hit in hits) {
NSDictionary* fields = [hit objectForKey:#"fields"];
[allResults addObject:fields];
}
self.searchResults = allResults;
[self.tableView reloadData];
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Failure fetching search results :-( %#", error);
}
];
[self.queue addOperation:operation];
return nil;
}];
I want to use this category to create UIImage GIFs from NSData. I don't want to use the dataWithContentsOfURL method as it blocks the main thread, so I want to use AFNetworking as I normally do to get the NSData.
With images, I've always done something like this:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:post.url]];
AFHTTPRequestOperation *imageOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
imageOperation.responseSerializer = [AFImageResponseSerializer serializer];
And then start it after a completion block. But that returns a UIImage. How can I asynchronously get NSData with AFNetworking?
Simply don't use the image serializer. I think this should work:
imageOperation.responseSerializer = [AFHTTPResponseSerializer serializer];
More about different AFNetworking serialization options here.
I just hacked a animated gif serialisation class together that uses Robs category.
It is — for sure — not complete, it will only work on iOS.
interface:
#import <AFNetworking/AFURLResponseSerialization.h>
#interface VSAnimatedGIFResponseSerializer : AFImageResponseSerializer
#end
implementation:
#import "VSAnimatedGIFResponseSerializer.h"
#import "UIImage+animatedGIF.h"
#implementation VSAnimatedGIFResponseSerializer
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [[NSSet alloc] initWithObjects: #"image/gif", nil];
return self;
}
+ (NSSet *)acceptablePathExtensions {
static NSSet * _acceptablePathExtension = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_acceptablePathExtension = [[NSSet alloc] initWithObjects:#"gif", nil];
});
return _acceptablePathExtension;
}
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if ([(NSError *)(*error) code] == NSURLErrorCannotDecodeContentData) {
return nil;
}
}
return [UIImage animatedImageWithAnimatedGIFData:data];
}
#end
Usage:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.bildschirmarbeiter.com/content/anigifs/animated-gifs-39/animated-gifs-39-001.gif"]];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [VSAnimatedGIFResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, UIImage *responseObject) {
NSLog(#"JSON: %#", responseObject);
UIImageView *imgView = [[UIImageView alloc] initWithImage:responseObject];
imgView.center = self.view.center;
[self.view addSubview:imgView];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[[NSOperationQueue mainQueue] addOperation:op];
}
You could use NSURLConnection's sendAsynchronousRequest:queue:completionHandler, whose completion block returns NSData.
Or I suppose you could convert the UIImage retrieved from the AFNetworking method to NSData with UIImagePNGRepresentation(image) but that is a bit less efficient.
I am having problems in saving a response coming from a POST request.
Based on AFnetworking documentation and NSScreencast tutorial I created my own subclass of AFHTTPRequestOperationManager, but I am not sure why the response is not saved.
How do I know, the response is not saved?
Because there is an error:(null) message in console and the my method does not perform segue.
I know that I am getting the values, because of the breakpoint that I put NSURLSessionDataTask
But I do not know why the values are not saved and I am getting an error message. I appreciate any help.
The APIClient/Manager
AuthAPIManager.h
#import "AFHTTPSessionManager.h"
#interface AuthAPIManager : AFHTTPSessionManager
+(AuthAPIManager *)sharedManager;
-(NSURLSessionDataTask *)initializeLogin:(NSString *)username completion:(void(^)(NSDictionary *results, NSError *error))completion;
//for login
#property (nonatomic,readonly,retain)NSString *StoreIdentifierForVendor;
#property(nonatomic,copy)NSString *devicetype;
#end
AuthAPIManager.m
#import "AuthAPIManager.h"
#import "LoginInfo.h"
#import "CredentialStore.h"
static AuthAPIManager *sharedManager =nil;
static dispatch_once_t onceToken;
#implementation AuthAPIManager
+(AuthAPIManager *)sharedManager
{
dispatch_once(&onceToken, ^{
sharedManager = [[AuthAPIManager alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]];
sharedManager.responseSerializer=[AFJSONResponseSerializer serializer];
sharedManager.requestSerializer = [AFJSONRequestSerializer serializer];
});
return sharedManager;
}
-(id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
}
return self;
}
-(NSURLSessionDataTask *)initializeLogin:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion
{
_devicetype = #"ios";
_StoreIdentifierForVendor = [[[UIDevice currentDevice]identifierForVendor]UUIDString];
id loginParameters =#{#"AccountId":username,
#"DeviceType":_devicetype
};
NSURLSessionDataTask *task =[self POST:#"/Accn" parameters:loginParameters
success:^(NSURLSessionDataTask *task, id responseObject)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if (httpResponse.statusCode == 200) {
LoginInfo *loginInfo =[[LoginInfo alloc]initWithDictionary:responseObject];
CredentialStore *credStore =[CredentialStore sharedStore];
credStore.loginInfo =loginInfo;
completion(responseObject,nil);
loginInfo = responseObject;
} else {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, nil);
});
NSLog(#"Received: %#", responseObject);
NSLog(#"Received HTTP %d", httpResponse.statusCode);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
}];
return task;
}
#end
And this is how I am calling my method in my view controller
- (IBAction)login:(id)sender
{
[_usernameTextField resignFirstResponder];
[SVProgressHUD show];
NSURLSessionDataTask *task = [[AuthAPIManager sharedManager] initializeLogin:self.usernameTextField.text completion:^(NSDictionary *results, NSError *error)
{
if (results) {
LoginInfo *loginInfo = [[LoginInfo alloc]initWithDictionary:results];
CredentialStore *credStore =[ CredentialStore sharedStore];
credStore.loginInfo =loginInfo;
[self performSegueWithIdentifier:#"welcomeViewSegue" sender:self];
}
else
{
NSLog(#"there is an error:%#",error);
}
}];
[SVProgressHUD dismiss];
}
Your success block is being called, but your status code is not 200.
You are calling your completion block like this:
completion(nil, nil);
So this code:
if (results) {
[snip]
}
else
{
NSLog(#"there is an error:%#",error);
}
is passed a nil results object.
Set a breakpoint on if (httpResponse.statusCode == 200) and inspect httpResponse in your debugger to see why it's not what you expect. (You may get a different success code, such as 204.)
Call your completion block with results instead of nil.
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;
}