887 lines
39 KiB
Objective-C
887 lines
39 KiB
Objective-C
/*
|
|
* Copyright 2010-present Facebook.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#import "SCViewController.h"
|
|
#import "SCAppDelegate.h"
|
|
#import "SCLoginViewController.h"
|
|
#import "SCProtocols.h"
|
|
#import <AddressBook/AddressBook.h>
|
|
#import "TargetConditionals.h"
|
|
#import "SCPhotoViewController.h"
|
|
|
|
@interface SCViewController() < UITableViewDataSource,
|
|
UIImagePickerControllerDelegate,
|
|
FBFriendPickerDelegate,
|
|
UINavigationControllerDelegate,
|
|
FBPlacePickerDelegate,
|
|
CLLocationManagerDelegate,
|
|
UIActionSheetDelegate> {
|
|
@private int _retryCount;
|
|
}
|
|
|
|
@property (strong, nonatomic) FBUserSettingsViewController *settingsViewController;
|
|
@property (strong, nonatomic) IBOutlet FBProfilePictureView *userProfileImage;
|
|
@property (strong, nonatomic) IBOutlet UILabel *userNameLabel;
|
|
@property (strong, nonatomic) IBOutlet UIButton *announceButton;
|
|
@property (strong, nonatomic) IBOutlet UITableView *menuTableView;
|
|
@property (strong, nonatomic) UIActionSheet *mealPickerActionSheet;
|
|
@property (retain, nonatomic) NSArray *mealTypes;
|
|
|
|
@property (strong, nonatomic) NSObject<FBGraphPlace> *selectedPlace;
|
|
@property (strong, nonatomic) NSString *selectedMeal;
|
|
@property (strong, nonatomic) NSArray *selectedFriends;
|
|
@property (strong, nonatomic) CLLocationManager *locationManager;
|
|
@property (strong, nonatomic) FBCacheDescriptor *placeCacheDescriptor;
|
|
@property (strong, nonatomic) UIActivityIndicatorView *activityIndicator;
|
|
|
|
@property (strong, nonatomic) UIImagePickerController *imagePicker;
|
|
@property (strong, nonatomic) UIActionSheet *imagePickerActionSheet;
|
|
@property (strong, nonatomic) UIImage *selectedPhoto;
|
|
@property (strong, nonatomic) UIPopoverController *popover;
|
|
@property (strong, nonatomic) SCPhotoViewController *photoViewController;
|
|
@property (nonatomic) CGRect popoverFromRect;
|
|
|
|
@end
|
|
|
|
@implementation SCViewController
|
|
|
|
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
|
|
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
|
if (self) {
|
|
// Custom initialization
|
|
self.navigationItem.hidesBackButton = YES;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Open Graph Helpers
|
|
|
|
// This is a helper function that returns an FBGraphObject representing a meal
|
|
- (id<SCOGMeal>)mealObjectForMeal:(NSString *)meal {
|
|
|
|
// We create an FBGraphObject object, but we can treat it as an SCOGMeal with typed
|
|
// properties, etc. See <FacebookSDK/FBGraphObject.h> for more details.
|
|
id<SCOGMeal> result = (id<SCOGMeal>)[FBGraphObject graphObject];
|
|
|
|
// Give it a URL of sample data that contains the object's name, title, description, and body.
|
|
// These OG object URLs were created using the edit open graph feature of the graph tool
|
|
// at https://www.developers.facebook.com/apps/
|
|
if ([meal isEqualToString:@"Cheeseburger"]) {
|
|
result.url = @"http://samples.ogp.me/314483151980285";
|
|
} else if ([meal isEqualToString:@"Pizza"]) {
|
|
result.url = @"http://samples.ogp.me/314483221980278";
|
|
} else if ([meal isEqualToString:@"Hotdog"]) {
|
|
result.url = @"http://samples.ogp.me/314483265313607";
|
|
} else if ([meal isEqualToString:@"Italian"]) {
|
|
result.url = @"http://samples.ogp.me/314483348646932";
|
|
} else if ([meal isEqualToString:@"French"]) {
|
|
result.url = @"http://samples.ogp.me/314483375313596";
|
|
} else if ([meal isEqualToString:@"Chinese"]) {
|
|
result.url = @"http://samples.ogp.me/314483421980258";
|
|
} else if ([meal isEqualToString:@"Thai"]) {
|
|
result.url = @"http://samples.ogp.me/314483451980255";
|
|
} else if ([meal isEqualToString:@"Indian"]) {
|
|
result.url = @"http://samples.ogp.me/314483491980251";
|
|
} else {
|
|
return nil;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (void)enableUserInteraction:(BOOL) enabled {
|
|
if (enabled) {
|
|
[self.activityIndicator stopAnimating];
|
|
} else {
|
|
[self centerAndShowActivityIndicator];
|
|
}
|
|
|
|
self.announceButton.enabled = enabled;
|
|
[self.view setUserInteractionEnabled:enabled];
|
|
}
|
|
|
|
- (id<SCOGEatMealAction>)actionFromMealInfo {
|
|
// Create an Open Graph eat action with the meal, our location, and the people we were with.
|
|
id<SCOGEatMealAction> action = (id<SCOGEatMealAction>)[FBGraphObject graphObject];
|
|
|
|
if (self.selectedPlace) {
|
|
// Facebook SDK * pro-tip *
|
|
// We don't use the action.place syntax here because, unfortunately, setPlace:
|
|
// and a few other selectors may be flagged as reserved selectors by Apple's App Store
|
|
// validation tools. While this doesn't necessarily block App Store approval, it
|
|
// could slow down the approval process. Falling back to the setObject:forKey:
|
|
// selector is a useful technique to avoid such naming conflicts.
|
|
[action setObject:self.selectedPlace forKey:@"place"];
|
|
}
|
|
|
|
if (self.selectedFriends.count > 0) {
|
|
[action setObject:self.selectedFriends forKey:@"tags"];
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
// Creates the Open Graph Action.
|
|
- (void)postOpenGraphAction {
|
|
[self enableUserInteraction:NO];
|
|
|
|
FBRequestConnection *requestConnection = [[FBRequestConnection alloc] init];
|
|
requestConnection.errorBehavior = FBRequestConnectionErrorBehaviorRetry
|
|
| FBRequestConnectionErrorBehaviorReconnectSession;
|
|
if (self.selectedPhoto) {
|
|
self.selectedPhoto = [self normalizedImage:self.selectedPhoto];
|
|
FBRequest *stagingRequest = [FBRequest requestForUploadStagingResourceWithImage:self.selectedPhoto];
|
|
[requestConnection addRequest:stagingRequest
|
|
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
|
|
if (error) {
|
|
[self enableUserInteraction:YES];
|
|
[self handlePostOpenGraphActionError:error];
|
|
}
|
|
}
|
|
batchEntryName:@"stagedphoto"];
|
|
}
|
|
|
|
// Create an Open Graph eat action with the meal, our location, and the people we were with.
|
|
id<SCOGEatMealAction> action = [self actionFromMealInfo];
|
|
|
|
if (self.selectedPhoto) {
|
|
action.image = @[ @{ @"url" : @"{result=stagedphoto:$.uri}", @"user_generated" : @"true" } ];
|
|
}
|
|
|
|
// create the Open Graph meal object for the meal we ate.
|
|
id<SCOGMeal> mealObject = [self mealObjectForMeal:self.selectedMeal];
|
|
if (mealObject) {
|
|
action.meal = mealObject;
|
|
} else {
|
|
// Facebook SDK * Object API *
|
|
id object = [FBGraphObject openGraphObjectForPostWithType:@"fb_sample_scrumps:meal"
|
|
title:self.selectedMeal
|
|
image:@"https://fbcdn-photos-a.akamaihd.net/photos-ak-snc7/v85005/200/233936543368280/app_1_233936543368280_595563194.gif"
|
|
url:nil
|
|
description:[@"Delicious " stringByAppendingString:self.selectedMeal]];
|
|
FBRequest *createObject = [FBRequest requestForPostOpenGraphObject:object];
|
|
|
|
// We'll add the object creaction to the batch, and set the action's meal accordingly.
|
|
[requestConnection addRequest:createObject
|
|
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
|
|
if (error) {
|
|
[self enableUserInteraction:YES];
|
|
[self handlePostOpenGraphActionError:error];
|
|
}
|
|
}
|
|
batchEntryName:@"createobject"];
|
|
|
|
action[@"meal"] = @"{result=createobject:$.id}";
|
|
}
|
|
|
|
// Create the request and post the action to the "me/fb_sample_scrumps:eat" path.
|
|
FBRequest *actionRequest = [FBRequest requestForPostWithGraphPath:@"me/fb_sample_scrumps:eat"
|
|
graphObject:action];
|
|
[requestConnection addRequest:actionRequest
|
|
completionHandler:^(FBRequestConnection *connection,
|
|
id result,
|
|
NSError *error) {
|
|
|
|
[self enableUserInteraction:YES];
|
|
if (!error) {
|
|
[[[UIAlertView alloc] initWithTitle:@"Result"
|
|
message:[NSString stringWithFormat:@"Posted Open Graph action, id: %@",
|
|
[result objectForKey:@"id"]]
|
|
delegate:nil
|
|
cancelButtonTitle:@"Thanks!"
|
|
otherButtonTitles:nil]
|
|
show];
|
|
|
|
// start over
|
|
[self resetMealInfo];
|
|
} else {
|
|
[self handlePostOpenGraphActionError:error];
|
|
}
|
|
}];
|
|
[requestConnection start];
|
|
}
|
|
|
|
- (void)resetMealInfo {
|
|
// start over
|
|
self.selectedMeal = nil;
|
|
self.selectedPlace = nil;
|
|
self.selectedFriends = nil;
|
|
self.selectedPhoto = nil;
|
|
_retryCount = 0;
|
|
[self updateSelections];
|
|
}
|
|
|
|
- (void)handlePostOpenGraphActionError:(NSError *) error{
|
|
// Facebook SDK * error handling *
|
|
// Some Graph API errors are retriable. For this sample, we will have a simple
|
|
// retry policy of one additional attempt. Please refer to
|
|
// https://developers.facebook.com/docs/reference/api/errors/ for more information.
|
|
_retryCount++;
|
|
if (error.fberrorCategory == FBErrorCategoryThrottling) {
|
|
// We also retry on a throttling error message. A more sophisticated app
|
|
// should consider a back-off period.
|
|
if (_retryCount < 2) {
|
|
NSLog(@"Retrying open graph post");
|
|
[self postOpenGraphAction];
|
|
return;
|
|
} else {
|
|
NSLog(@"Retry count exceeded.");
|
|
}
|
|
}
|
|
|
|
// Facebook SDK * pro-tip *
|
|
// Users can revoke post permissions on your app externally so it
|
|
// can be worthwhile to request for permissions again at the point
|
|
// that they are needed. This sample assumes a simple policy
|
|
// of re-requesting permissions.
|
|
if (error.fberrorCategory == FBErrorCategoryPermissions) {
|
|
NSLog(@"Re-requesting permissions");
|
|
[self requestPermissionAndPost];
|
|
return;
|
|
}
|
|
|
|
// Facebook SDK * error handling *
|
|
[self presentAlertForError:error];
|
|
}
|
|
// Helper method to request publish permissions and post.
|
|
- (void)requestPermissionAndPost {
|
|
[FBSession.activeSession requestNewPublishPermissions:[NSArray arrayWithObject:@"publish_actions"]
|
|
defaultAudience:FBSessionDefaultAudienceFriends
|
|
completionHandler:^(FBSession *session, NSError *error) {
|
|
if (!error && [FBSession.activeSession.permissions indexOfObject:@"publish_actions"] != NSNotFound) {
|
|
// Now have the permission
|
|
[self postOpenGraphAction];
|
|
} else if (error){
|
|
// Facebook SDK * error handling *
|
|
// if the operation is not user cancelled
|
|
if (error.fberrorCategory != FBErrorCategoryUserCancelled) {
|
|
[self presentAlertForError:error];
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void) presentAlertForError:(NSError *)error {
|
|
// Facebook SDK * error handling *
|
|
// Error handling is an important part of providing a good user experience.
|
|
// When fberrorShouldNotifyUser is YES, a fberrorUserMessage can be
|
|
// presented as a user-ready message
|
|
if (error.fberrorShouldNotifyUser) {
|
|
// The SDK has a message for the user, surface it.
|
|
[[[UIAlertView alloc] initWithTitle:@"Something Went Wrong"
|
|
message:error.fberrorUserMessage
|
|
delegate:nil
|
|
cancelButtonTitle:@"OK"
|
|
otherButtonTitles:nil] show];
|
|
} else {
|
|
NSLog(@"unexpected error:%@", error);
|
|
}
|
|
}
|
|
|
|
#pragma mark - UI Behavior
|
|
|
|
-(void)settingsButtonWasPressed:(id)sender {
|
|
[self presentLoginSettings];
|
|
}
|
|
|
|
-(void)presentLoginSettings {
|
|
if (self.settingsViewController == nil) {
|
|
self.settingsViewController = [[FBUserSettingsViewController alloc] init];
|
|
#ifdef __IPHONE_7_0
|
|
if ([self.settingsViewController respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
|
|
self.settingsViewController.edgesForExtendedLayout &= ~UIRectEdgeTop;
|
|
}
|
|
#endif
|
|
self.settingsViewController.delegate = self;
|
|
}
|
|
|
|
[self.navigationController pushViewController:self.settingsViewController animated:YES];
|
|
}
|
|
|
|
// Handles the user clicking the Announce button by creating an Open Graph Action
|
|
- (IBAction)announce:(id)sender {
|
|
if (FBSession.activeSession.isOpen) {
|
|
// Facebook SDK * pro-tip *
|
|
// Ask for publish permissions only at the time they are needed.
|
|
if ([FBSession.activeSession.permissions indexOfObject:@"publish_actions"] == NSNotFound) {
|
|
[self requestPermissionAndPost];
|
|
} else {
|
|
[self postOpenGraphAction];
|
|
}
|
|
} else {
|
|
// Facebook SDK * pro-tip *
|
|
// Support sharing even if the user isn't logged in with Facebook, by using the share dialog
|
|
[self presentShareDialogForMealInfo];
|
|
}
|
|
}
|
|
|
|
- (void)presentShareDialogForMealInfo {
|
|
id image = @"https://fbcdn-photos-a.akamaihd.net/photos-ak-snc7/v85005/200/233936543368280/app_1_233936543368280_595563194.gif";
|
|
// Create an Open Graph eat action with the meal, our location, and the people we were with.
|
|
id<SCOGEatMealAction> action = [self actionFromMealInfo];
|
|
|
|
if (self.selectedPhoto) {
|
|
self.selectedPhoto = [self normalizedImage:self.selectedPhoto];
|
|
action.image = self.selectedPhoto;
|
|
image = @[@{@"url":self.selectedPhoto, @"user_generated":@"true"}];
|
|
}
|
|
|
|
id object = [FBGraphObject openGraphObjectForPostWithType:@"fb_sample_scrumps:meal"
|
|
title:self.selectedMeal
|
|
image:image
|
|
url:nil
|
|
description:[@"Delicious " stringByAppendingString:self.selectedMeal]];
|
|
action.meal = object;
|
|
|
|
BOOL presentable = nil != [FBDialogs presentShareDialogWithOpenGraphAction:action
|
|
actionType:@"fb_sample_scrumps:eat"
|
|
previewPropertyName:@"meal"
|
|
handler:^(FBAppCall *call, NSDictionary *results, NSError *error) {
|
|
if (!error) {
|
|
[self resetMealInfo];
|
|
} else {
|
|
NSLog(@"%@", error);
|
|
}
|
|
}];
|
|
if (!presentable) { // this means that the Facebook app is not installed or up to date
|
|
// if the share dialog is not available, lets encourage a login so we can share directly
|
|
[self presentLoginSettings];
|
|
}
|
|
}
|
|
|
|
- (void)centerAndShowActivityIndicator {
|
|
CGRect frame = self.view.frame;
|
|
CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
|
|
self.activityIndicator.center = center;
|
|
[self.activityIndicator startAnimating];
|
|
}
|
|
|
|
// Displays the user's name and profile picture so they are aware of the Facebook
|
|
// identity they are logged in as.
|
|
- (void)populateUserDetails {
|
|
if (FBSession.activeSession.isOpen) {
|
|
[[FBRequest requestForMe] startWithCompletionHandler:
|
|
^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
|
|
if (!error) {
|
|
self.userNameLabel.text = user.name;
|
|
self.userProfileImage.profileID = [user objectForKey:@"id"];
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
#pragma mark UIImagePickerControllerDelegate methods
|
|
|
|
- (void)imagePickerController:(UIImagePickerController *)picker
|
|
didFinishPickingImage:(UIImage *)image
|
|
editingInfo:(NSDictionary *)editingInfo {
|
|
|
|
if (!self.photoViewController) {
|
|
__block SCViewController *myself = self;
|
|
self.photoViewController = [[SCPhotoViewController alloc]initWithNibName:@"SCPhotoViewController" bundle:nil image:image];
|
|
self.photoViewController.confirmCallback = ^(id sender, bool confirm) {
|
|
if(confirm) {
|
|
myself.selectedPhoto = image;
|
|
}
|
|
[myself updateSelections];
|
|
};
|
|
}
|
|
[self.navigationController pushViewController:self.photoViewController animated:true];
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
|
[self.popover dismissPopoverAnimated:YES];
|
|
} else {
|
|
[self dismissModalViewControllerAnimated:YES];
|
|
}
|
|
self.photoViewController = nil;
|
|
}
|
|
|
|
#pragma mark - UIActionSheetDelegate methods
|
|
|
|
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
|
|
|
|
// If user presses cancel, do nothing
|
|
if (buttonIndex == actionSheet.cancelButtonIndex)
|
|
return;
|
|
|
|
// One method handles the delegate action for two action sheets
|
|
if (actionSheet == self.mealPickerActionSheet) {
|
|
if (buttonIndex == 0) {
|
|
// They chose manual entry so prompt the user for an entry.
|
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"What are you eating?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
|
|
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
|
|
[alert textFieldAtIndex:0].autocapitalizationType = UITextAutocapitalizationTypeSentences;
|
|
[alert show];
|
|
} else {
|
|
self.selectedMeal = [self.mealTypes objectAtIndex:buttonIndex];
|
|
[self updateSelections];
|
|
}
|
|
} else { // self.imagePickerActionSheet
|
|
NSAssert(actionSheet == self.imagePickerActionSheet, @"Delegate method's else-case should be for image picker");
|
|
|
|
if (!self.imagePicker) {
|
|
self.imagePicker = [[UIImagePickerController alloc] init];
|
|
self.imagePicker.delegate = self;
|
|
}
|
|
|
|
// Set the source type of the imagePicker to the users selection
|
|
if (buttonIndex == 0) {
|
|
// If its the simulator, camera is no good
|
|
if(TARGET_IPHONE_SIMULATOR){
|
|
[[[UIAlertView alloc] initWithTitle:@"Camera not supported in simulator."
|
|
message:@"(>'_')>"
|
|
delegate:nil
|
|
cancelButtonTitle:@"Ok"
|
|
otherButtonTitles:nil] show];
|
|
return;
|
|
}
|
|
self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
|
|
} else if (buttonIndex == 1) {
|
|
self.imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
|
}
|
|
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
|
// Can't use presentModalViewController for image picker on iPad
|
|
if (!self.popover) {
|
|
self.popover = [[UIPopoverController alloc] initWithContentViewController:self.imagePicker];
|
|
}
|
|
[self.popover presentPopoverFromRect:self.popoverFromRect
|
|
inView:self.view
|
|
permittedArrowDirections:UIPopoverArrowDirectionAny
|
|
animated:YES];
|
|
} else {
|
|
[self presentModalViewController:self.imagePicker animated:YES];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - Overrides
|
|
|
|
- (void)dealloc {
|
|
_locationManager.delegate = nil;
|
|
_mealPickerActionSheet.delegate = nil;
|
|
_settingsViewController.delegate = nil;
|
|
_imagePicker.delegate = nil;
|
|
_imagePickerActionSheet.delegate = nil;
|
|
}
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
self.title = @"Scrumptious";
|
|
|
|
// Get the CLLocationManager going.
|
|
self.locationManager = [[CLLocationManager alloc] init];
|
|
self.locationManager.delegate = self;
|
|
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
|
|
// We don't want to be notified of small changes in location, preferring to use our
|
|
// last cached results, if any.
|
|
self.locationManager.distanceFilter = 50;
|
|
|
|
FBCacheDescriptor *cacheDescriptor = [FBFriendPickerViewController cacheDescriptor];
|
|
[cacheDescriptor prefetchAndCacheForSession:FBSession.activeSession];
|
|
|
|
// This avoids a gray background in the table view on iPad.
|
|
if ([self.menuTableView respondsToSelector:@selector(backgroundView)]) {
|
|
self.menuTableView.backgroundView = nil;
|
|
}
|
|
|
|
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
|
|
initWithTitle:@"Settings"
|
|
style:UIBarButtonItemStyleBordered
|
|
target:self
|
|
action:@selector(settingsButtonWasPressed:)];
|
|
|
|
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
|
self.activityIndicator.hidesWhenStopped = YES;
|
|
[self.view addSubview:self.activityIndicator];
|
|
}
|
|
|
|
-(void)viewWillAppear:(BOOL)animated {
|
|
[super viewWillAppear:animated];
|
|
|
|
if (FBSession.activeSession.isOpen) {
|
|
[self populateUserDetails];
|
|
self.userProfileImage.hidden = NO;
|
|
} else {
|
|
self.userProfileImage.hidden = YES;
|
|
}
|
|
}
|
|
|
|
- (void) viewDidAppear:(BOOL)animated {
|
|
if (FBSession.activeSession.isOpen) {
|
|
[self.locationManager startUpdatingLocation];
|
|
}
|
|
}
|
|
|
|
- (void)viewDidUnload {
|
|
[super viewDidUnload];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
// Release any retained subviews of the main view.
|
|
self.mealPickerActionSheet = nil;
|
|
}
|
|
|
|
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
|
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
|
|
}
|
|
|
|
#pragma mark - UIAlertViewDelegate
|
|
|
|
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
|
|
if (!alertView.cancelButtonIndex == buttonIndex) {
|
|
self.selectedMeal = [alertView textFieldAtIndex:0].text;
|
|
[self updateSelections];
|
|
}
|
|
}
|
|
|
|
#pragma mark - FBUserSettingsDelegate methods
|
|
|
|
- (void)loginViewControllerDidLogUserOut:(id)sender {
|
|
// Facebook SDK * login flow *
|
|
// There are many ways to implement the Facebook login flow.
|
|
// In this sample, the FBLoginView delegate (SCLoginViewController)
|
|
// will already handle logging out so this method is a no-op.
|
|
}
|
|
|
|
- (void)loginViewController:(id)sender receivedError:(NSError *)error{
|
|
// Facebook SDK * login flow *
|
|
// There are many ways to implement the Facebook login flow.
|
|
// In this sample, the FBUserSettingsViewController is only presented
|
|
// as a log out option after the user has been authenticated, so
|
|
// no real errors should occur. If the FBUserSettingsViewController
|
|
// had been the entry point to the app, then this error handler should
|
|
// be as rigorous as the FBLoginView delegate (SCLoginViewController)
|
|
// in order to handle login errors.
|
|
if (error) {
|
|
NSLog(@"Unexpected error sent to the FBUserSettingsViewController delegate: %@", error);
|
|
}
|
|
}
|
|
|
|
#pragma mark - UITableViewDataSource methods and related helpers
|
|
|
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
|
return 4;
|
|
}
|
|
|
|
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
static NSString *CellIdentifier = @"Cell";
|
|
|
|
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
|
if (cell == nil) {
|
|
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
|
|
cell.accessoryType = UITableViewCellAccessoryNone;
|
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
|
|
|
cell.textLabel.font = [UIFont systemFontOfSize:16];
|
|
cell.textLabel.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
|
|
cell.textLabel.lineBreakMode = UILineBreakModeTailTruncation;
|
|
cell.textLabel.clipsToBounds = YES;
|
|
|
|
cell.detailTextLabel.font = [UIFont systemFontOfSize:12];
|
|
cell.detailTextLabel.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
|
|
cell.detailTextLabel.textColor = [UIColor colorWithRed:0.4 green:0.6 blue:0.8 alpha:1];
|
|
cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
|
|
cell.detailTextLabel.clipsToBounds = YES;
|
|
}
|
|
|
|
switch (indexPath.row) {
|
|
case 0:
|
|
cell.textLabel.text = @"What are you eating?";
|
|
cell.detailTextLabel.text = @"Select one";
|
|
cell.imageView.image = [UIImage imageNamed:@"action-eating.png"];
|
|
break;
|
|
|
|
case 1:
|
|
cell.textLabel.text = @"Where are you?";
|
|
cell.detailTextLabel.text = @"Select one";
|
|
cell.imageView.image = [UIImage imageNamed:@"action-location.png"];
|
|
break;
|
|
|
|
case 2:
|
|
cell.textLabel.text = @"With whom?";
|
|
cell.detailTextLabel.text = @"Select friends";
|
|
cell.imageView.image = [UIImage imageNamed:@"action-people.png"];
|
|
break;
|
|
|
|
case 3:
|
|
cell.textLabel.text = @"Got a picture?";
|
|
cell.detailTextLabel.text = @"Take one";
|
|
cell.imageView.image = [UIImage imageNamed:@"action-photo.png"];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return cell;
|
|
}
|
|
|
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
|
return 1;
|
|
}
|
|
|
|
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
return NO;
|
|
}
|
|
|
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
switch (indexPath.row) {
|
|
case 0: {
|
|
// if we don't yet have an array of meal types, create one now
|
|
if (!self.mealTypes) {
|
|
self.mealTypes = [NSArray arrayWithObjects:
|
|
@"< Write Your Own >",
|
|
@"Cheeseburger",
|
|
@"Pizza",
|
|
@"Hotdog",
|
|
@"Italian",
|
|
@"French",
|
|
@"Chinese",
|
|
@"Thai",
|
|
@"Indian",
|
|
nil];
|
|
}
|
|
self.mealPickerActionSheet = [[UIActionSheet alloc] initWithTitle:@"Select a meal"
|
|
delegate:self
|
|
cancelButtonTitle:nil
|
|
destructiveButtonTitle:nil
|
|
otherButtonTitles:nil];
|
|
|
|
for( NSString *meal in self.mealTypes) {
|
|
[self.mealPickerActionSheet addButtonWithTitle:meal];
|
|
}
|
|
|
|
self.mealPickerActionSheet.cancelButtonIndex = [self.mealPickerActionSheet addButtonWithTitle:@"Cancel"];
|
|
[self.mealPickerActionSheet showFromToolbar:self.navigationController.toolbar];
|
|
return;
|
|
}
|
|
|
|
case 1: {
|
|
if (FBSession.activeSession.isOpen) {
|
|
FBPlacePickerViewController *placePicker = [[FBPlacePickerViewController alloc] init];
|
|
|
|
placePicker.title = @"Select a restaurant";
|
|
|
|
// SIMULATOR BUG:
|
|
// See http://stackoverflow.com/questions/7003155/error-server-did-not-accept-client-registration-68
|
|
// at times the simulator fails to fetch a location; when that happens rather than fetch a
|
|
// a meal near 0,0 -- let's see if we can find something good in Paris
|
|
if (self.placeCacheDescriptor == nil) {
|
|
[self setPlaceCacheDescriptorForCoordinates:CLLocationCoordinate2DMake(48.857875, 2.294635)];
|
|
}
|
|
|
|
[placePicker configureUsingCachedDescriptor:self.placeCacheDescriptor];
|
|
[placePicker loadData];
|
|
[placePicker presentModallyFromViewController:self
|
|
animated:YES
|
|
handler:^(FBViewController *sender, BOOL donePressed) {
|
|
if (donePressed) {
|
|
self.selectedPlace = placePicker.selection;
|
|
[self updateSelections];
|
|
}
|
|
}];
|
|
} else {
|
|
// if not logged in, give the user the option to log in
|
|
[self presentLoginSettings];
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 2: {
|
|
if (FBSession.activeSession.isOpen) {
|
|
FBFriendPickerViewController *friendPicker = [[FBFriendPickerViewController alloc] init];
|
|
|
|
// Set up the friend picker to sort and display names the same way as the
|
|
// iOS Address Book does.
|
|
|
|
// Need to call ABAddressBookCreate in order for the next two calls to do anything.
|
|
ABAddressBookRef addressBook = ABAddressBookCreate();
|
|
ABPersonSortOrdering sortOrdering = ABPersonGetSortOrdering();
|
|
ABPersonCompositeNameFormat nameFormat = ABPersonGetCompositeNameFormat();
|
|
|
|
friendPicker.sortOrdering = (sortOrdering == kABPersonSortByFirstName) ? FBFriendSortByFirstName : FBFriendSortByLastName;
|
|
friendPicker.displayOrdering = (nameFormat == kABPersonCompositeNameFormatFirstNameFirst) ? FBFriendDisplayByFirstName : FBFriendDisplayByLastName;
|
|
|
|
[friendPicker loadData];
|
|
[friendPicker presentModallyFromViewController:self
|
|
animated:YES
|
|
handler:^(FBViewController *sender, BOOL donePressed) {
|
|
if (donePressed) {
|
|
self.selectedFriends = friendPicker.selection;
|
|
[self updateSelections];
|
|
}
|
|
}];
|
|
CFRelease(addressBook);
|
|
} else {
|
|
// if not logged in, give the user the option to log in
|
|
[self presentLoginSettings];
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 3:
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
|
self.popoverFromRect = [tableView rectForRowAtIndexPath:indexPath];
|
|
}
|
|
if(!self.imagePickerActionSheet) {
|
|
self.imagePickerActionSheet = [[UIActionSheet alloc] initWithTitle:@""
|
|
delegate:self
|
|
cancelButtonTitle:@"Cancel"
|
|
destructiveButtonTitle:nil
|
|
otherButtonTitles:@"Take Photo", @"Choose Existing", nil];
|
|
}
|
|
|
|
[self.imagePickerActionSheet showInView:self.view];
|
|
return;
|
|
}
|
|
}
|
|
|
|
- (void)updateCellIndex:(int)index withSubtitle:(NSString *)subtitle {
|
|
UITableViewCell *cell = (UITableViewCell *)[self.menuTableView cellForRowAtIndexPath:
|
|
[NSIndexPath indexPathForRow:index inSection:0]];
|
|
cell.detailTextLabel.text = subtitle;
|
|
}
|
|
|
|
- (void)updateSelections {
|
|
[self updateCellIndex:0 withSubtitle:(self.selectedMeal ?
|
|
self.selectedMeal :
|
|
@"Select one")];
|
|
[self updateCellIndex:1 withSubtitle:(self.selectedPlace ?
|
|
self.selectedPlace.name :
|
|
@"Select one")];
|
|
|
|
NSString *friendsSubtitle = @"Select friends";
|
|
int friendCount = self.selectedFriends.count;
|
|
if (friendCount > 2) {
|
|
// Just to mix things up, don't always show the first friend.
|
|
id<FBGraphUser> randomFriend = [self.selectedFriends objectAtIndex:arc4random() % friendCount];
|
|
friendsSubtitle = [NSString stringWithFormat:@"%@ and %d others",
|
|
randomFriend.name,
|
|
friendCount - 1];
|
|
} else if (friendCount == 2) {
|
|
id<FBGraphUser> friend1 = [self.selectedFriends objectAtIndex:0];
|
|
id<FBGraphUser> friend2 = [self.selectedFriends objectAtIndex:1];
|
|
friendsSubtitle = [NSString stringWithFormat:@"%@ and %@",
|
|
friend1.name,
|
|
friend2.name];
|
|
} else if (friendCount == 1) {
|
|
id<FBGraphUser> friend = [self.selectedFriends objectAtIndex:0];
|
|
friendsSubtitle = friend.name;
|
|
}
|
|
[self updateCellIndex:2 withSubtitle:friendsSubtitle];
|
|
|
|
[self updateCellIndex:3 withSubtitle:(self.selectedPhoto ? @"Ready" : @"Take one")];
|
|
|
|
self.announceButton.enabled = (self.selectedMeal != nil);
|
|
}
|
|
|
|
#pragma mark - CLLocationManagerDelegate methods and related
|
|
|
|
- (void)locationManager:(CLLocationManager *)manager
|
|
didUpdateToLocation:(CLLocation *)newLocation
|
|
fromLocation:(CLLocation *)oldLocation {
|
|
if (!oldLocation ||
|
|
(oldLocation.coordinate.latitude != newLocation.coordinate.latitude &&
|
|
oldLocation.coordinate.longitude != newLocation.coordinate.longitude &&
|
|
newLocation.horizontalAccuracy <= 100.0)) {
|
|
// Fetch data at this new location, and remember the cache descriptor.
|
|
[self setPlaceCacheDescriptorForCoordinates:newLocation.coordinate];
|
|
[self.placeCacheDescriptor prefetchAndCacheForSession:FBSession.activeSession];
|
|
}
|
|
}
|
|
|
|
- (void)locationManager:(CLLocationManager *)manager
|
|
didFailWithError:(NSError *)error {
|
|
NSLog(@"%@", error);
|
|
}
|
|
|
|
- (void)setPlaceCacheDescriptorForCoordinates:(CLLocationCoordinate2D)coordinates {
|
|
self.placeCacheDescriptor =
|
|
[FBPlacePickerViewController cacheDescriptorWithLocationCoordinate:coordinates
|
|
radiusInMeters:1000
|
|
searchText:@"restaurant"
|
|
resultsLimit:50
|
|
fieldsForRequest:nil];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (UIImage*) normalizedImage:(UIImage*)image {
|
|
CGImageRef imgRef = image.CGImage;
|
|
CGFloat width = CGImageGetWidth(imgRef);
|
|
CGFloat height = CGImageGetHeight(imgRef);
|
|
CGAffineTransform transform = CGAffineTransformIdentity;
|
|
CGRect bounds = CGRectMake(0, 0, width, height);
|
|
CGSize imageSize = bounds.size;
|
|
CGFloat boundHeight;
|
|
UIImageOrientation orient = image.imageOrientation;
|
|
|
|
switch (orient) {
|
|
case UIImageOrientationUp: //EXIF = 1
|
|
transform = CGAffineTransformIdentity;
|
|
break;
|
|
|
|
case UIImageOrientationDown: //EXIF = 3
|
|
transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
|
|
transform = CGAffineTransformRotate(transform, M_PI);
|
|
break;
|
|
|
|
case UIImageOrientationLeft: //EXIF = 6
|
|
boundHeight = bounds.size.height;
|
|
bounds.size.height = bounds.size.width;
|
|
bounds.size.width = boundHeight;
|
|
transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
|
|
transform = CGAffineTransformScale(transform, -1.0, 1.0);
|
|
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
|
|
break;
|
|
|
|
case UIImageOrientationRight: //EXIF = 8
|
|
boundHeight = bounds.size.height;
|
|
bounds.size.height = bounds.size.width;
|
|
bounds.size.width = boundHeight;
|
|
transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
|
|
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
|
|
break;
|
|
|
|
default:
|
|
// image is not auto-rotated by the photo picker, so whatever the user
|
|
// sees is what they expect to get. No modification necessary
|
|
transform = CGAffineTransformIdentity;
|
|
break;
|
|
}
|
|
|
|
UIGraphicsBeginImageContext(bounds.size);
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
|
|
if ((image.imageOrientation == UIImageOrientationDown) ||
|
|
(image.imageOrientation == UIImageOrientationRight) ||
|
|
(image.imageOrientation == UIImageOrientationUp)) {
|
|
// flip the coordinate space upside down
|
|
CGContextScaleCTM(context, 1, -1);
|
|
CGContextTranslateCTM(context, 0, -height);
|
|
}
|
|
|
|
CGContextConcatCTM(context, transform);
|
|
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
|
|
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
|
|
return imageCopy;
|
|
}
|
|
|
|
@end
|