问题
I'm working on a feature for an iOS app that will enable its user to pick a photo from their Facebook gallery.
I've got the initial request to get the photos working - it does return a small number of photos and a link to the next and previous batches. My problem is I don't know what the right way of handling this pagination is; I spent a long time trying to google it or find an answer in Facebook's documentation but it's simply rubbish (i.e. no help whatsoever).
Could you take a look at the method that is supposed to deal with this request and explain to me how to add the rest of the photos to the usersFacebookPhotos mutable array?
NSMutableArray *usersFacebookPhotos;
- (void) getUserPhotoAlbumsWithSuccess:(void (^) (bool))successHandler failure:(void (^) (NSError *error))failureHandler {
usersFacebookPhotos = (NSMutableArray *)[[NSArray alloc] init];
FBRequest *fbRequest = [FBRequest requestWithGraphPath:@"me?fields=photos.fields(picture,source)" parameters:nil HTTPMethod:@"GET"];
[fbRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSLog(@"got the initial batch");
// process the next batch of photos here
}
else {
NSLog(@"error: %@", error);
}
}];
}
Oh, and yes - I tried using grabKit but decided not to spend any more time trying to set it up - I followed the instructions to the letter but it would still throw errors.
回答1:
I've used a recursive function call to solve this problem and I've set a low limit of 10 to test the functionality.
-(void)facebookCall {
[self getFBFriends:@"me/friends?fields=name,picture.type(large)&limit=10"];
}
-(void)getFBFriends:(NSString*)url {
[FBRequestConnection startWithGraphPath:url
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
[self parseFBResult:result];
NSDictionary *paging = [result objectForKey:@"paging"];
NSString *next = [paging objectForKey:@"next"];
// skip the beginning of the url https://graph.facebook.com/
// there's probably a more elegant way of doing this
NSLog(@"next:%@", [next substringFromIndex:27]);
[self getFBFriends:[next substringFromIndex:27]];
} else {
NSLog(@"An error occurred getting friends: %@", [error localizedDescription]);
}
}];
}
-(void)parseFBResult:(id)result {
NSLog(@"My friends: %@", result);
NSArray *data = [result objectForKey:@"data"];
int j = 0;
for(NSDictionary *friend in data){
NSDictionary *picture = [friend objectForKey:@"picture"];
NSDictionary *picData = [picture objectForKey:@"data"];
NSLog(@"User:%@, picture URL: %@", [friend objectForKey:@"name"], [picData objectForKey:@"url"]);
j++;
}
NSLog(@"No of friends is: %d", j);
}
回答2:
This is mostly based on my research carried out using the trial-and-error method as Facebook's documentation wasn't helpful at all. I'm happy to learn of a better method of doing this :)
We can then use the calls from the Graph Explorer in the template code:
NSString *yourCall = @”YourGraphExplorerCall”;
FBRequest *fbRequest = [FBRequest requestWithGraphPath:yourCall parameters:nil HTTPMethod:@"GET"];
[fbRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSDictionary *jsonResponse = (NSDictionary *) result;
// Do your stuff with the JSON response
}
else {
failureHandler(error);
}
}];
Facebook Graph accepts and replies in JSON.
Obtaining a user’s albums and photos - the problem of pagination
Once a user is logged in their session is handled behind the scenes by the Facebook API so there’s no need to be concerned about that, we can just perform specific requests we want.
To get the albums data for a user, put this string into the Graph Explorer:
me?fields=albums.fields(count,id)
This’ll ask Fb for the count of photos in each album and their IDs. Notice that the 1st level of the JSON reply contains the ID of the user as well as the “albums” array which contains the “data” array - that’s the array of the actual albums we’re interested in.
Having the IDs of each album we can explore their photos. The below call will get links to each album’s source photo and its miniature:
<album_id>?fields=photos.fields(source,picture)
where is the actual ID of the album whose photos you want to get.
The initial problem is that since there might be many photos in an album, trying to get them in one go is probably a bad idea - that’s why Facebook devs introduced pagination into these calls. What this means is that you can set a limit for the number of photos data you get in a single call and you then use a “cursor” for the next/previous batches that you want to get, with said cursors being given to you in each call. The main problem is dealing with such paginated data. If we look at the data returned in our previous call we can see that there’s a “paging” section with “cursors” (which contains “before” and “after”) and “next”. The “next” key is a link which looks very similar to our call string used in the Graph Explorer - and it ends with the “after” cursor; we could think, then, that it’s possible to simply append the “after” cursor to our call string
<album_id>?fields=photos.fields(source,picture)&after=<after_cursor>
and feed that into the Graph Explorer. Nope! For some reason this won’t work as expected - it still directs us to the first batch instead of the next one. However, the “next” link still works so it’s possible to use parts of it instead of the call we made to the Graph Explorer. Thus the call to get the photos:
<album_id>?fields=photos.fields(source,picture)
becomes:
<album_id>/photos?fields=source%2Cpicture&limit=25
Moreover, it still works after being appended with &after=:
<album_id>/photos?fields=source%2Cpicture&limit=25&after=
Thus it’s easy to simply get the value of “next” in each call for a batch and append it to the above string for the next call.
Here’s the snippet of the final version of the code:
NSString *const FACEBOOK_GRAPH_LIST_ALBUMS = @"me?fields=albums.fields(count,id,name)";
NSString *const FACEBOOK_GRAPH_LIST_ALBUM_PHOTOS = @"/photos?fields=source%2Cpicture&limit=25&after=";
NSArray *currentUsersFacebookAlbums;
- (void) getUserPhotosWithSuccess:(void (^) ())successHandler failure:(void (^) (NSError *error))failureHandler {
FBRequest *fbRequest = [FBRequest requestWithGraphPath:FACEBOOK_GRAPH_LIST_ALBUMS parameters:nil HTTPMethod:@"GET"];
[fbRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSDictionary *jsonResponse = (NSDictionary *) result;
currentUsersFacebookAlbums = (NSArray *) [[jsonResponse valueForKey:@"albums"] valueForKey:@"data"];
for (NSDictionary *currentAlbum in currentUsersFacebookAlbums) {
NSString *albumId = [currentAlbum valueForKey:@"id"];
[self getCurrentUserFacebookPhotosWithAlbum:albumId afterCursor:nil failure:^(NSError *error) {
failureHandler(error);
}];
}
successHandler();
}
else {
failureHandler(error);
}
}];
}
- (void) getCurrentUserFacebookPhotosWithAlbum:(NSString *) albumId afterCursor:(NSString *) afterCursor failure:(void (^) (NSError *error))failureHandler {
if (afterCursor == nil) {
afterCursor = @"";
}
NSString *fbGraphCall = [NSString stringWithFormat:@"%@%@%@", albumId, FACEBOOK_GRAPH_LIST_ALBUM_PHOTOS, afterCursor];
FBRequest *fbRequest = [FBRequest requestWithGraphPath:fbGraphCall parameters:nil HTTPMethod:@"GET"];
[fbRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSDictionary *jsonResponse = (NSDictionary *) result;
NSArray *currentPhotoBatch = (NSArray *) [jsonResponse valueForKey:@"data"];
// Go through the currently obtained batch and add them to the returned mutable array
for (NSDictionary *currentPhoto in currentPhotoBatch) {
[[CurrentUserDataHandler sharedInstance] addFacebookPhoto:currentPhoto];
}
// If there's a "next" link in the response, recur the method on the next batch...
if ([[jsonResponse valueForKey:@"paging"] objectForKey:@"next"] != nil) {
// ...by appending the "after" cursor to the call
NSString *afterCursor = [[[jsonResponse valueForKey:@"paging"] valueForKey:@"cursors"] valueForKey:@"after"];
[self getCurrentUserFacebookPhotosWithAlbum:albumId afterCursor:afterCursor failure:^(NSError *error) {
failureHandler(error);
}];
}
if ([[jsonResponse valueForKey:@"paging"] objectForKey:@"next"] != nil && [self isLastAlbum:albumId]) {
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_FACEBOOK_PHOTOS object:nil];
}
}
else {
failureHandler(error);
}
}];
}
- (bool) isLastAlbum:(NSString *) albumId {
for (NSDictionary *albumData in currentUsersFacebookAlbums) {
if ([albumId isEqualToString:[albumData valueForKey:@"id"]] && [currentUsersFacebookAlbums indexOfObject:albumData] == [currentUsersFacebookAlbums count] - 1) {
return YES;
}
}
return NO;
}
回答3:
For pagination of facebook I would suggest to use apple native class as
Use nextPageURL variable to cache the next url from JSON response and assign to url string on next api request if nextPageURL is not nil and use following piece of code:
if (self.nextPageURL) {
// urlString is the first time formulated url string
urlString = self.nextPageURL;
}
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[NSURLConnection sendAsynchronousRequest:request queue:networkQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
DDLogVerbose(@"FACEBOOK:Connection error occured: %@",error.description);
}else{
isRequestProcessing = NO;
NSDictionary *resultData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
DDLogVerbose(@"parsed data is %@",resultData);
self.nextPageURL = resultData[@"paging"][@"next"];
// do your customisation of resultData here.
}
}
}];
来源:https://stackoverflow.com/questions/20476795/handle-pagination-of-facebook-user-photos