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
Related
I'm trying to implement background fetch as well as refresh in iOS 10.
I'm using XML parsing to parse the data and then storing it in a file in the document's directory. For parsing XML I'm using a custom class (XMLParser) that confirms the NSXMLParserDelegate protocol.
The background fetch works fine. But I'm having problems in displaying the refreshed data, both when I click on the refresh button as well as in viewDidLoad.
I'm calling the refreshData method in viewDidLoad.
Here's how far I've gotten.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//--Set background fetch--//
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}
...
#pragma mark Background data fetch methods
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSDate *fetchStart = [NSDate date];
ArtsViewController *artsViewController = (ArtsViewController *)self.window.rootViewController;
[artsViewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
completionHandler(result);
NSDate *fetchEnd = [NSDate date];
NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
NSLog(#"Background Fetch Duration: %f seconds", timeElapsed);
}];
}
ArtsViewController.h
#interface ArtsViewController : UIViewController <UIPageViewControllerDataSource>
#property BOOL newsAvailable;
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; // No problems here
#end
ArtsViewcontroller.m
#interface ArtsViewController ()
#property (nonatomic, strong) NSArray *arrNewsData;
-(void)refreshData;
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray;
#end
...
#implementation ArtsViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self refreshData];
//--Load the file that saves news--//
[self loadNews];
if (_newsAvailable == YES)
{
[self setupPageViewController];
}
else
{
[self showNoNewsMessage];
}
}
...
#pragma mark Data Fetch methods
-(void)refreshData{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
[self performNewFetchedDataActionsWithDataArray:dataArray];
}
else{
NSLog(#"%#", [error localizedDescription]);
}
}];
}
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray{
// 1. Initialize the arrNewsData array with the parsed data array.
if (self.arrNewsData != nil) {
self.arrNewsData = nil;
}
self.arrNewsData = [[NSArray alloc] initWithArray:dataArray];
// 2. Write the file and reload the view.
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docDirectory = [paths objectAtIndex:0];
NSString * newsFilePath = [NSString stringWithFormat:#"%#",[docDirectory stringByAppendingPathComponent:#"arts2"]]; // NewsFile
if (![self.arrNewsData writeToFile:newsFilePath atomically:YES]) {
_newsAvailable = NO;
NSLog(#"Couldn't save data.");
}
else
{
_newsAvailable = YES;
NSLog(#"Saved data.");
[self viewWillAppear:YES];
}
}
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
NSDictionary *latestDataDict = [dataArray objectAtIndex:0];
NSString *latestTitle = [latestDataDict objectForKey:#"title"];
NSDictionary *existingDataDict = [self.arrNewsData objectAtIndex:0];
NSString *existingTitle = [existingDataDict objectForKey:#"title"];
if ([latestTitle isEqualToString:existingTitle]) {
completionHandler(UIBackgroundFetchResultNoData);
NSLog(#"No new data found.");
}
else{
[self performNewFetchedDataActionsWithDataArray:dataArray];
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"New data was fetched.");
}
}
else{
completionHandler(UIBackgroundFetchResultFailed);
NSLog(#"Failed to fetch new data.");
}
}];
}
...
#pragma mark IBActions
- (IBAction)reloadNews:(UIBarButtonItem *)sender
{
[self viewDidLoad];
}
I've debugged the application and found that after viewDidLoad
completes execution, the data file is written but the view isn't
updated. I've also tried calling the refreshData method in the main
thread, but there's no change.
after viewDidLoad is complete the showNoNewNews method is called.
I'm suspecting that my logic isn't wrong but implementation is. Threads at play here..
Any help would be appreciated.
Update:
Hope this helps those with similar problems...
I moved the logic of viewDidLoad to a different method, called the method for the first time in viewDidLoad and again in refreshData, after
[self performNewFetchedDataActionsWithDataArray:dataArray];
When I login to my app, my app does push the ViewController XYZMainViewController, XYZMainViewController viewWillAppear:animated call method that makes a request to my API to retrieve the authenticated user data, at this time I update the text of a label to show the user name. When I logout the app, it returns me to the login ViewController, when I do login again with another user, XYZMainViewController label text contains the name of the previous user, without updating the label text.
XYZMainViewController.m
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self homeProfile];
}
- (void)homeProfile
{
[NXOAuth2Request performMethod:#"GET"
onResource:[NSURL URLWithString:#"http://{url}/users/userinfo"]
usingParameters:nil
withAccount:[XYZCommonFunctions user]
sendProgressHandler:nil
responseHandler:^(NSURLResponse *response, NSData *responseData, NSError *error){
NSDictionary *parsedData = [[NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error] objectForKey:#"data"];
_user = [parsedData objectForKey:#"user"];
[self.label setText:[NSString stringWithFormat:#"Welcome %#!", [_user objectForKey:#"username"]]];
}];
}
- (IBAction)logout:(id)sender {
XYZAppDelegate* appDelegate = (XYZAppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate logout];
}
XYZAppDelegate.m
- (void)login
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *identifier = [prefs stringForKey:#"accountidentifier"];
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
NSString *viewIdentifier = #"WelcomeView";
if(identifier != nil){
NXOAuth2Account *account = [[NXOAuth2AccountStore sharedStore] accountWithIdentifier:identifier];
if(account != nil) {
viewIdentifier = #"MainView";
}
UIViewController *controller = [mainStoryboard instantiateViewControllerWithIdentifier: viewIdentifier];
[navigationController pushViewController:controller animated:NO];
return;
}
}
- (void)logout
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs removeObjectForKey:#"accountidentifier"];
[prefs synchronize];
for (NXOAuth2Account *a in [[NXOAuth2AccountStore sharedStore] accounts] ){
[[NXOAuth2AccountStore sharedStore] removeAccount:a];
}
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
[navigationController popToRootViewControllerAnimated:YES];
}
I need to reinitialize all data in XYZMainViewController.
Thank you.
Look like problem is related to fetching JSON Object. It is possible that everytime you have send same user to fetch user data. You are not using NSUserdefault object to display name, you are using value, which is return by JSON Object. According to me cause of error is "withAccount:[XYZCommonFunctions user]" line.
I would like to suggest, instead of using
-(void)viewWillAppear:(BOOL)animated {
you can use
- (void)viewDidLoad
so that your login action performed only when your LoginController loads,instead when LoginController appear.
New viewwillAppear look as given below -
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.label setText:#""];
}
and ViewDidLoad -
- (void)viewDidLoad
{
[super viewDidLoad];
[self homeProfile];
}
Also check your json response, whether you are getting response success or error.According to response need to handle.
Hope this helps.
Hello in Ios i am using Sttwitter Api
in view i call to twitter page and then user can login and after login call appDelegate page
and in appDelegate.m file call to this function
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
// NSLog(#"%#",url );
NSLog(#"%#",[url scheme]);
if ([[url scheme] isEqualToString:#"fb1428424747377519"])
{
return [FBAppCall handleOpenURL:url
sourceApplication:sourceApplication
withSession:self.session];
return YES;
}
else if ([[url scheme] isEqualToString:#"myapp"])
{
NSDictionary *d = [self parametersDictionaryFromQueryString:[url query]];
NSString *token = d[#"oauth_token"];
NSString *verifier = d[#"oauth_verifier"];
HomeViewController *vc = (HomeViewController *)[[self window] rootViewController];
NSLog(#"%#",token);
NSLog(#"%#",verifier);
[vc setOAuthToken:token oauthVerifier:verifier];
return YES;
}else
{
return NO;
}
in this code [vc setOAuthToken:token oauthVerifier:verifier]; my app is cash
in homeviewController.h i have add this method
- (void)setOAuthToken:(NSString *)token oauthVerifier:(NSString *)verfier;
and HomeViewController.m
- (void)setOAuthToken:(NSString *)token oauthVerifier:(NSString *)verifier {
[_twitter postAccessTokenRequestWithPIN:verifier successBlock:^(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName) {
NSLog(#"-- screenName: %#", screenName);
NSLog(#"-- userID: %#", userID);
//name=screenName;
// [self saveYourDescription];
// [self timeline];
// [self getimage];
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
}
and Error is
2014-06-12 17:52:03.834[3302:90b] -[UINavigationController setOAuthToken:oauthVerifier:]: unrecognized selector sent to instance 0x10acc3af0
2014-06-12 17:52:03.864[3302:90b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController setOAuthToken:oauthVerifier:]: unrecognized selector sent to instance 0x10acc3af0'
please Help me
It seems that vc in not an instance of HomeViewController but UINavigationController instead.
You have to send the setOAuthToken:oauthVerifier: message to a HomeViewController instance.
Your HomeViewController not look like a "rootViewController", as #nst mentioned.
One possible solution is use delegate to call setOAuthToken method.
For this particular example you files will change as
Declare a delegate of HomeViewController in AppDelegate.h after #interface
AppDelegate.h
#property (assign) id home_delegate;
And in AppDelegate.m replace this line
HomeViewController *vc = (HomeViewController *)[[self window] rootViewController];
With
[home_delegate setOAuthToken:token oauthVerifier:verifier];
P.S: Don't forget to assign delegate to self in HomeViewController
Use this Simple code and Just Replace with your Code It's Working
for (UIViewController *vc in ((UINavigationController *)[[self window] rootViewController]).viewControllers) {
if ([vc isKindOfClass:[ViewController class]]) {
ViewController *viewController = (ViewController *)vc;
[viewController setOAuthToken:token oauthVerifier:verifier];
}
}
I currently have a class and ViewController with a button action to get the username and password from a textfield and put them into their own NSString. I then use the NSString to perform a post request like so.
NSString *user = _username.text;
NSString *password = _password.text;
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://thesite.com/login.php"]];
[httpClient setParameterEncoding:AFFormURLParameterEncoding];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST"
path:#"http://thesite.com/login.php"
parameters:#{#"username":user, #"password":password}];
AFHTTPRequestOperation * httpOperation = [httpClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
//success code
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//error handler
}];
[httpClient enqueueHTTPRequestOperation:httpOperation];
However I have another class and Viewcontroller to perform a get request. However, in order to perform this get request I need to get the "NSString *user" from the first View Controller. How would I go about doing this? Should I declare a NSString *user in the header of the first Viewcontroller and then in the second View controller declare an instance of the first class?
You can pass strings through viewcontrollers. Make therefore a segue between the two viewcontroller and named it for example "secondVC"
the when you want to switch to other view make this call
[self performSegueWithIdentifier:#"secondVC"];
and implement this method.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"secondVC"]) {
SecondViewController *second = (SecondViewController *)[segue destinationViewController];
second.userString = self.user;
}
}
You can use NSUserDefaults to store the username and use it in other ViewControllers.
For example, save it in your current ViewController.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:username forKey:#"UserName"];
[defaults synchronize];
get username it in another ViewController.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *username = [defaults objectForKey:#"UserName"];
pass the userName to second view controller.
You can achieve it using following approaches.
try to pass the username when you are initialising your secondViewController like
SecondViewController * sVC = [SecondViewController alloc] initWithUserName: username];
for this in your SecondViewController class you have to modify the init method and add a property of userName {typeOF string}
#property (nonatomic, strong) NSString * userName
- (id) initWithUserName: (NSString *) name
{
self = [super initWithNibName: #"SecondViewController"
bundle: nil];
if (self)
{
self.userName = name;
}
return self;
}
Enjoy!!
Create object of first class in second class like this..
//First class
//Pass the values to seconClass
secondClass *appdelegate = [NSApp delegate];
[appdelegate initwithDetails:userName withPassword:password];
//Second class
//In second class declare the function and get the values
//When u create the object, init function will call first
-(id)initwithDetails:(NSString *)user withPassword:(NSString *)password
{
userName = [NSString stringWithFormat:#"%#", user];
newPass=[NSString stringWithFormat:#"%#", password];
return self;
}
If the second viewcontroller is spawned from the first you could just create a delegate protocol and corresponding delegate so that either the first is delegate of the second or the second is delegate of the first:
Here is an example of a protocol which includes one method, notice the instance variable delegate is of type id, as it will be unknown at compile time the type of class that will adopt this protocol.
#import <Foundation/Foundation.h>
#protocol ProcessDataDelegate <NSObject>
#required
- (void) processSuccessful: (BOOL)success;
#end
#interface ClassWithProtocol : NSObject
{
id <ProcessDataDelegate> delegate;
}
#property (retain) id delegate;
-(void)startSomeProcess;
#end
Inside the implementation section for the interface defined above we need to do two things at a minimum – first synthesize the delegate instance variable and second, call the method defined in the protocol as needed (more on that in a moment).
Let’s look at a bare bones implementation of the ClassWithProtocol.m:
#import "ClassWithProtocol.h"
#implementation ClassWithProtocol
#synthesize delegate;
- (void)processComplete
{
[[self delegate] processSuccessful:YES];
}
-(void)startSomeProcess
{
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self
selector:#selector(processComplete) userInfo:nil repeats:YES];
}
#end
Read more at this tutorial
I am using GMTOAuth2 in an application with one viewController and it works fine, Then I add same code to another application that contains more viewControllers, It loads the webView with textfields for username and password but if I click on anything in this view it crashes with Thread 1: EXC_BAD_ACCESS (code=2, address=0xbf7ffffc) with void SendDelegateMessage(NSInvocation *): delegate (webView:decidePolicyForNavigationAction:request:frame:decisionListener:) failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode in the console. My code for authorization part looks like this:
- (void)awakeFromNib {
[super awakeFromNib];
GTMOAuth2Authentication *auth = nil;
auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:kMyClientID
clientSecret:kMyClientSecret];
if (auth)
{
[auth authorizeRequest:nil
delegate:self
didFinishSelector:#selector(authentication:request:finishedWithError:)];
}
}
- (void)signInToGoogle {
[self signOut];
NSString *keychainItemName = kKeychainItemName;
NSString *scope = #"https://spreadsheets.google.com/feeds https://docs.google.com/feeds";
NSString *clientID = kMyClientID;
NSString *clientSecret = kMyClientSecret;
if ([clientID length] == 0 || [clientSecret length] == 0) {
NSString *msg = #"The sample code requires a valid client ID and client secret to sign in.";
[self displayAlertWithMessage:msg];
return;
}
SEL finishedSel = #selector(viewController:finishedWithAuth:error:);
GTMOAuth2ViewControllerTouch *viewController = [[GTMOAuth2ViewControllerTouch alloc] initWithScope:scope
clientID:clientID
clientSecret:clientSecret
keychainItemName:keychainItemName
finishedSelector:finishedSel];
delegate:self
[self presentViewController:viewController animated:YES completion:^{}];
[[self navigationController] pushViewController:viewController animated:YES];
}
- (void)authentication:(GTMOAuth2Authentication *)auth
request:(NSMutableURLRequest *)request
finishedWithError:(NSError *)error {
if (error != nil) {
NSLog(#"error!");
} else {
[self isAuthorizedWithAuthentication:auth];
self.auth = auth;
}
}
And copied the same class from first application to second one, and made exactly same nib file.