I'm trying to save the username and user email after a user logs into my app through facebook.
I have set up a segue to pass the info from the login view controller to the view controller where I plan to save the user strings and some other strings into my sqlite database.
When I do run my app in the destination view controller and try to insert all the data into sqlite, I am thrown an error saying the userName and the userEmail are nil strings, so my data is not being saved. I have correctly set up the segue; synthesized, added properties. But it seems to me the problem may be how I'm retrieving the data. Help would be greatly appreciated! Thank you in advance!
-(void)prepareForSegue:(UIStoryboardSegue *)segue user:(id<FBGraphUser>)user sender:(id)sender{
if([segue.identifier isEqualToString:#"loginInfo"]) {
NSString *name = user.name;
NSString *email = [user objectForKey:#"email"];
ViewController *vc = (ViewController *)[segue destinationViewController];
vc.userName = name;
vc.userEmail = email;
NSLog(#"user data is being prepared to segue");
}
}
You used -(void)prepareForSegue:(UIStoryboardSegue *)segue user:(id)user sender:(id)sender
There is no such method for segue unwinding in UIViewController class. Instead of that Use -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"loginInfo"]) {
NSString *name = user.name;
NSString *email = [user objectForKey:#"email"];
ViewController *vc = (ViewController *)[segue destinationViewController];
vc.userName = name;
vc.userEmail = email;
NSLog(#"user data %#, email %#",name,email);
}
}
To get the user info you need to implement facebook delegate.
- (void)meRequestResult:(id)result WithError:(NSError *)error
{
NSLog(#"result %#",result );
if ([result isKindOfClass:[NSDictionary class]])
{
NSDictionary *dictionary;
if([result objectForKey:#"data"])
dictionary = (NSDictionary *)[(NSArray *)[result objectForKey:#"data"] objectAtIndex:0];
else
dictionary = (NSDictionary *)result;
email = [dictionary valueForKey:#"email"];
fName = [dictionary valueForKey:#"first_name"];
lName = [dictionary valueForKey:#"last_name"];
}
}
Related
I would like to show ChatViewController via a tab bar controller. The current initial view for the app is a NavigationController that loads the ChatViewController. When the ChatViewController is loaded, it checks to see if the ‘joinedchat’ method was called. If not, it presents LoginViewController to allow users to authenticate into the ChatViewController. When the user authenticates, LoginViewController is dismissed.
The LoginViewController and the ComposeViewController, are modal view controllers that are displayed on top of the ChatViewController.
I would like to access this ChatViewController at a much later point in the storyboard, while keeping it as the rootviewcontroller so it can still preserve the data model it uses for classes in anticipation of the $_POST method it uses.
Instead of presenting the LoginViewController if joinedchat hasn’t yet been called, I am showing a different view controller. About 4 view controllers later, after the user has gone on a different process, I use a tab bar controller to access the LoginViewController again. When I try to call the postUpdateRequest method to access the ChatViewController, the app crashes with the output in the debugger:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[1]'
I suspect it’s because the app uses a strict data model that sets snd stores default versions of the strings that will be posted by the user from LoginViewController via postJoinRequest. Does anyone know any ways to authenticate users using this data?
AppDelegate.m - didRegisterForRemoteNotificationsWithDeviceToken
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
UINavigationController *navigationController = (UINavigationController*)_window.rootViewController;
ChatViewController *chatViewController = (ChatViewController*)[navigationController.viewControllers objectAtIndex:0];
DataModel *dataModel = chatViewController.dataModel;
NSString* oldToken = [dataModel deviceToken];
NSString* newToken = [deviceToken description];
newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
newToken = [newToken stringByReplacingOccurrencesOfString:#" " withString:#""];
NSLog(#"My token is: %#", newToken);
[dataModel setDeviceToken:newToken];
if ([dataModel joinedChat] && ![newToken isEqualToString:oldToken])
{
[self postUpdateRequest];
}
}
AppDelegate.m - PostUpdateRequest
- (void)postUpdateRequest
{
UINavigationController *navigationController = (UINavigationController*)_window.rootViewController;
ChatViewController *chatViewController = (ChatViewController*)[navigationController.viewControllers objectAtIndex:0];
DataModel *dataModel = chatViewController.dataModel;
NSDictionary *params = #{#"cmd":#"update",
#"user_id":[dataModel userId],
#"token":[dataModel deviceToken]};
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:ServerApiURL]];
[client
postPath:#"/api.php"
parameters:params
success:nil failure:nil];
}
DataModel.m - initializer
+ (void)initialize
{
if (self == [DataModel class])
{
// Register default values for our settings
[[NSUserDefaults standardUserDefaults] registerDefaults:
#{NicknameKey: #"",
SecretCodeKey: #"",
JoinedChatKey: #0,
DeviceTokenKey: #"0",
UserId:#""}];
}
}
DataModel.m - userId
- (NSString*)userId
{
NSString *userId = [[NSUserDefaults standardUserDefaults] stringForKey:UserId];
if (userId == nil || userId.length == 0) {
userId = [[[NSUUID UUID] UUIDString] stringByReplacingOccurrencesOfString:#"-" withString:#""];
[[NSUserDefaults standardUserDefaults] setObject:userId forKey:UserId];
}
return userId;
}
LoginViewController.h (Update)
#class DataModel;
// The Login screen lets the user register a nickname and chat room
#interface LoginViewController : UIViewController
#property (nonatomic, assign) DataModel* dataModel;
#property (nonatomic, strong) AFHTTPClient *client;
#end
LoginViewController.m - postJoinRequest & loginAction
- (void)postJoinRequest
{
MBProgressHUD* hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = NSLocalizedString(#"Connecting", nil);
NSDictionary *params = #{#"cmd":#"join",
#"user_id":[_dataModel userId],
#"token":[_dataModel deviceToken],
#"name":[_dataModel nickname],
#"code":[_dataModel secretCode]};
[_client postPath:#"/api.php"
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
if ([self isViewLoaded]) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
if([operation.response statusCode] != 200) {
ShowErrorAlert(NSLocalizedString(#"There was an error communicating with the server", nil));
} else {
[self userDidJoin];
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
if ([self isViewLoaded]) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
ShowErrorAlert([error localizedDescription]);
}
}];
}
- (IBAction)loginAction
{
if (self.nicknameTextField.text.length == 0)
{
ShowErrorAlert(NSLocalizedString(#"Fill in your nickname", nil));
return;
}
if (self.secretCodeTextField.text.length == 0)
{
ShowErrorAlert(NSLocalizedString(#"Fill in a secret code", nil));
return;
}
[self.dataModel setNickname:self.nicknameTextField.text];
[self.dataModel setSecretCode:self.secretCodeTextField.text];
// Hide the keyboard
[self.nicknameTextField resignFirstResponder];
[self.secretCodeTextField resignFirstResponder];
[self postJoinRequest];
}
ChatViewController.h (Update)
#import "ComposeViewController.h"
#class DataModel;
// The main screen of the app. It shows the history of all messages that
// this user has sent and received. It also opens the Compose screen when
// the user wants to send a new message.
#interface ChatViewController : UITableViewController <ComposeDelegate>
#property (nonatomic, strong, readonly) DataModel* dataModel;
#end
Update Terminal Output
I created a present modally segues in my storyboard like in the image below.
My First scene has a button with an action:
- (IBAction)loginButtonPressed:(id)sender {
[self Login];
}
which goes into this method:
- (void)Login
{
NSString *rawString = [self.idTextField text];
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
[self.idTextField setText:[rawString stringByTrimmingCharactersInSet:whitespace]];
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text completionHandler:^(id responseObject, NSError *error) {
if (responseObject != nil) {
NSString *userN,*name;
NSArray *object = [responseObject objectAtIndex:0];
userN = [object valueForKey:#"userName"];
name = [object valueForKey:#"name"];
self.idTextField = nil;
self.passwordTextField = nil;
LHAppDelegate *appDelegate = (LHAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.loginSession setString:[userN lowercaseString]];
[appDelegate.nameOfUser setString:name];
}else{
[self CustomAlert:#"Sorry Login Failed, User and/or Passsword Incorrect"];
}
[indicatorView stopAnimating];
[indicatorView removeFromSuperview];
indicatorView = nil;
[loadingView removeFromSuperview];
loadingView = nil;
}];
}
What I am trying to do is program so when the user logins in they are present modally to the Tab Bar Controller.
Currently, when I click on the button with the wrong creds, I see my custom alert, but it still brings me to the Tab bar Controller.
Basically what I wanna do is put a condition around the present modally segues. Is this possible?
I found this piece of code:
[self performSegueWithIdentifier: #"MySegue" sender: self];
and I added it to my Login method:
- (void)Login
{
NSString *rawString = [self.idTextField text];
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
[self.idTextField setText:[rawString stringByTrimmingCharactersInSet:whitespace]];
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text completionHandler:^(id responseObject, NSError *error) {
if (responseObject != nil) {
NSString *userN,*name;
NSArray *object = [responseObject objectAtIndex:0];
userN = [object valueForKey:#"userName"];
name = [object valueForKey:#"name"];
self.idTextField = nil;
self.passwordTextField = nil;
LHAppDelegate *appDelegate = (LHAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.loginSession setString:[userN lowercaseString]];
[appDelegate.nameOfUser setString:name];
[self performSegueWithIdentifier: #"MySegue" sender: self];
}else{
[self CustomAlert:#"Sorry Login Failed, User and/or Passsword Incorrect"];
}
[indicatorView stopAnimating];
[indicatorView removeFromSuperview];
indicatorView = nil;
[loadingView removeFromSuperview];
loadingView = nil;
}];
}
still when I login with the wrong creds and it still brings me to the Tab Bar Controller
In the above scenario it will always perform segue unaffected by server response because the segue is performed by button click.
To avoid this do it in the following way:
Delete the segue from button to the UITabBarController and make it
from view controller to the UITabBarController(drag from view
controller to other view conroller).
Now when your getting success in response make the following call
[self performSegueWithIdentifier:#"segueIdentifier" sender:self]
And if you want to pass parameters the perform the following function:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
One easy way of doing it would be to implement - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender from the NSSeguePerforming protocol.
If user was not logged in return NO, else return YES, and segue will be performed/user will be taken to the tab bar controller.
EDIT
Since you already have implemented logic for authorising user in login method, I suggest doing what #manish_kumar have suggested.
I have two views :
View1 (Shop) : URL stocked in NSString for displaying image.
View2 (ModifyShop) : Text field with URL from view1.
I can pass data from view1 to view2 : The URL stocked in NSString appears in Text field and pass data from view2 to view1. (URL for example)
Now I would like to modify this URL with Text field from view2 and that modify the NSString in view1 for ever. How can I make that ? Using NSUserDefaults ?
Here is my code :
Shop:
- (void)viewDidLoad {
[super viewDidLoad];
[self.modifyButton setHidden:YES];
}
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"admin"]) {
NSLog(#"pas admin");
} else {
[self.modifyButton setHidden:NO];
}
dispatch_async(dispatch_get_global_queue(0,0), ^{
self.imageButtonURL = #"http://bundoransurfshop.com/wp-content/uploads/2015/02/72-torq-pink.jpg";
imageButtonData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:self.imageButtonURL]];
if ( imageButtonData == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
self.imageButton.imageView.image = [UIImage imageWithData: imageButtonData];
});
});
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"modifyShop"]) {
ShopModify *viewcontroller = (ShopModify *)segue.destinationViewController;
viewcontroller.imageButtonURL = self.imageButtonURL; }
}
-(IBAction)prepareForUnwindShopModify:(UIStoryboardSegue *)segue {
NSLog(#"%#", self.imageButtonURL);
}
Shopmodify:
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
self.photoURL.text = [NSString stringWithFormat:#"%#", self.imageButtonURL];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
Shop *viewcontroller = (Shop *)segue.destinationViewController;
viewcontroller.imageButtonURL = self.photoURL.text;
}
You can store a URL string in NSUserDefaults by following this post:
Save string to the NSUserDefaults?
Simply have your view1 always get it from NSUserDefaults in viewWillAppear and have view2 change the NSUserDefaults key for the URL string and that should do it
If with foverer you mean even after you close and open again your app, replacing the default http://bundoransurfshop.com/wp-content/uploads/2015/02/72-torq-pink.jpg, yes, for example you could use NSUserDefaults (or any other persistent way to store your data/configuration).
Save on edit in view2:
[[NSUserDefaults standardUserDefaults] setObject:valueToSave
forKey:#"imageUrl"];
[[NSUserDefaults standardUserDefaults] synchronize]; //Important
Load/Reload in view1:
NSString *savedUrl = [[NSUserDefaults standardUserDefaults] stringForKey:#"imageUrl"];
if(!savedValue){
savedUrl = #"http://bundoransurfshop.com/wp-content/uploads/2015/02/72-torq-pink.jpg";
}
Official documentation for NSUserDefaults.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CatagoryToSetDetail"]){
BYFSetsDetailViewController *controller = (BYFSetsDetailViewController *)segue.destinationViewController;
controller.workOut.catagory = catagory.text;
controller.workOut.excercise = excercize.text;
NSLog(#"passing the values %# \n and %# \n ",controller.workOut.catagory,controller.workOut.excercise);
}
for some reason controller.workOut.category is nil and i dont know how to fix it.
here is how i instantiated it in view did load
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.workOut = [[BYFWorkOut alloc]init];
NSLog(#"recieved values %# \n and %# \n ",_workOut.catagory,_workOut.excercise);
}
Thank you any help would be appreciated
prepareForSegue performed before viewDidLoad and yours workOut is nil when you tries assign category and excersize
Solution 1:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CatagoryToSetDetail"]){
BYFSetsDetailViewController *controller = (BYFSetsDetailViewController *)segue.destinationViewController;
controller.workOut.catagory = catagory.text;
controller.workOut.excercise = excercize.text;
NSLog(#"passing the values %# \n and %# \n ",controller.workOut.catagory,controller.workOut.excercise);
}
in BYFSetsDetailViewController:
#implementation BYFSetsDetailViewController
#synthesyze workOut = _workOut;
- (id) initWithCoder:(NSCoder*) encoder
{
self = [super initWithCoder:encoder];
if (self)
{
_workOut = [[BYFWorkOut alloc] init];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"recieved values %# \n and %# \n ",_workOut.catagory,_workOut.excercise);
}
Solution 2:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CatagoryToSetDetail"]){
BYFSetsDetailViewController *controller = (BYFSetsDetailViewController *)segue.destinationViewController;
controller.workOut = [[BYFWorkOut alloc]init];
controller.workOut.catagory = catagory.text;
controller.workOut.excercise = excercize.text;
NSLog(#"passing the values %# \n and %# \n ",controller.workOut.catagory,controller.workOut.excercise);
}
in BYFSetsDetailViewController:
#implementation BYFSetsDetailViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"recieved values %# \n and %# \n ",_workOut.catagory,_workOut.excercise);
}
Have you checked that category.text and exercise.text are not nil? - as those are the values you're passing to the destinationViewController in this method.
Also, where have you instantiated catagory.text and excercize.text ?
Edit
It looks like your custom object workOut has not been instantiated correctly. Do this in your init or viewDidLoad method of BYFSetsDetailViewController
Edit 2
Okay I got it work like this (Not sure why, maybe someone can explain more)
In your BYFSetsDetailViewController make a public property of NSString (I assume, workOut is an NSString?)
Then in your prepareForSegue method do this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CatagoryToSetDetail"]){
BYFSetsDetailViewController *controller = (BYFSetsDetailViewController *)segue.destinationViewController;
controller.myString = catagory.text;
controller.myString2 = excercize.text;
NSLog(#"passing the values %# \n and %# \n ",controller.workOut.catagory,controller.workOut.excercise);
}
Declare your strings like this: #property (copy, nonatomic) NSString *myString
Then in your viewDidLoad method of BYFSetsDetailViewController
Do this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.workOut = [[BYFWorkOut alloc]init];
self.workOut.catagory = self.myString;
self.workOut.excercize = self.myString2;
NSLog(#"recieved values %# \n and %# \n ",_workOut.catagory,_workOut.excercise);
}
This should solve your issue.
Edit 3
From Cy-4AH's answer - do this in your prepareForSegue
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CatagoryToSetDetail"]){
BYFSetsDetailViewController *controller = (BYFSetsDetailViewController *)segue.destinationViewController;
controller.workOut = [[BYFWorkOut alloc]init];
controller.workOut.catagory = catagory.text;
controller.workOut.excercise = excercize.text;
NSLog(#"passing the values %# \n and %# \n ",controller.workOut.catagory,controller.workOut.excercise);
Remember to import BYFWorkOut in this viewController at the top of the file.
Im trying to update a label in the next view with an annotations title on segue, Im not sure how to do this, but working on the lines of this. Any suggestion or does this need to be done where the annotation is created?
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"pushShare"])
{
ShareViewController *vc = (ShareViewController *)segue.destinationViewController;
[vc fromLabel.text = StartAnnotation.title];
}
}
Update
The annotation is created when a button is clicked, a pin is dropped on the user location and the annotation title displays the address, Iv tried updating a label in the same view with the title but having trouble with that aswell.
code for creating the annotation
CLLocationCoordinate2D theCoordinate = {_map.userLocation.location.coordinate.latitude,_map.userLocation.location.coordinate.longitude};
CLLocation *currentLocation = [[CLLocation alloc]
initWithLatitude:_map.userLocation.location.coordinate.latitude
longitude:_map.userLocation.location.coordinate.longitude];
NSLog(#"self.geocoder=%#", self.geocoder);
[self.geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemark, NSError *error) {
NSString *address = #"Address unknown";
NSLog(#"geocoder error=%#", error);
if (placemark.count > 0)
{
CLPlacemark *topResult = [placemark objectAtIndex:0];
address = [NSString stringWithFormat:#"%# %# %# %#", topResult.subThoroughfare, topResult.thoroughfare, topResult.subLocality, topResult.locality];
}
StartAnnotation *startPoint = [[StartAnnotation alloc]init];
startPoint.coordinate = theCoordinate;
startPoint.title = address;
startPoint.subtitle = #"Start Point";
[self.map addAnnotation:startPoint];
[self.map selectAnnotation:startPoint animated:YES];
}];
//e.g. fromLabel.text = StartAnnotation.title;
You can handle didSelectAnnotationView to detect when a user tapped on your MKAnnotationView, like in this example:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
[mapView deselectAnnotation:view.annotation animated:YES];
[self performSegueWithIdentifier:YOUR_SEGUE_ID
sender:view];
}
Then in prepareForSegue extract the title of the annotation and use it:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:YOUR_SEGUE_ID])
{
UIView *annotationView = (UIView *)sender;
ShareViewController *vc = (ShareViewController *)segue.destinationViewController;
vc.textToSetOnTheLabel = annotationView.annotation.title;
}
}