Instructing an app on which view to open on load? - objective-c

I am trying to instruct my app to open a certain view, depending on whether the user has already created a user profile on my database.
so basically -
- (void) viewWillAppear:(BOOL)animated {
//all my ASIHTTPRequest code here
//blah blah blah
NSString *responseString = [request responseString];
if ([responseString isEqualToString:#"noexistingdata"])
{
FriendsViewController *friendsview = [[FriendsViewController alloc] initWithNibName:nil bundle:nil];
//SOMETHING NEEDS TO GO HERE TO MAKE THIS WORK!
} else if ([responseString isEqualToString:#"success"])
{
//do whatever
}
}
I just want the most basic code for changing a view... I would try using IBAction, but obviously that won't work as this is in a void for the app's launch (rather a response to a button the user presses), also thought about void in a void, which also did not work.
So basically what I need is:
Launch App > App receives response from my server > (IF RESPONSE = "THIS", LOAD VIEW "X") (IF RESPONSE = "THAT", LOAD VIEW "Y")
Anyone have a clue?
PS: would it be better to list this is applicationDidFinishLaunching?

-viewWillAppear: is called just before a view is displayed. That's probably not a great time to a) run some synchronous network request and b) load another view controller.
I'd suggest having the application delegate create a view controller who's view just displays a message, like "Connecting to server" or "Retrieving user credentials" or whatever. You might add an animated spinner to let the user know that they're supposed to wait, if that's all they're allowed to do at this point. Then, after the view is displayed, such as in that controller's -viewDidLoad method, start an asynchronous network connection to talk to the server. When you get back a response, decide which view controller to instantiate next, and do that.

Related

Attempt to present UITabBarController whose view is not in the view hierarchy - Firebase, Swift

I'm working on the login system for an app, and when a user registers, I would like it to go straight "into" the app. Meaning, not back to the login screen, and then redirected "into" the app, or prompted to then login after registering. The desired outcome is working with the following code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//The disallowed character set is the inverse of the allowed characters
disallowedUsernameCharacters = allowedUsernameCharacters.invertedSet
//Set the delegates
confirmPassTextField.delegate = self
passwordTextField.delegate = self
usernameTextField.delegate = self
emailTextField.delegate = self
//Observe authentication events, if the authentication is successful, perform the segue
ref.observeAuthEventWithBlock { (authData) -> Void in
if authData != nil {
//Use standard defaults
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
//Store the uid
prefs.setObject(authData.uid as String, forKey: "UID")
self.performSegueWithIdentifier(self.successfulSignUp, sender: nil)
}
}
}
In which if the user registers, and is authenticated, it will perform the segue to the UITabBarController which contains 5 tabs/views for the app itself. This works fine, but I am given the Warning: Attempt to present on whose view is not in the window hierarchy!
From doing some research, this seems to be a fairly common warning that people get. However, I would like to fix it so that all portions of the app will behave as expected. I found that some people recommended putting the code portion:
//Observe authentication events, if the authentication is successful, perform the segue
ref.observeAuthEventWithBlock { (authData) -> Void in
if authData != nil {
//Use standard defaults
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
//Store the uid
prefs.setObject(authData.uid as String, forKey: "UID")
self.performSegueWithIdentifier(self.successfulSignUp, sender: nil)
}
}
Inside of the viewWillAppear method because of the current view potentially not being on the hierarchy at the time the segue is called, but I don't think that is the fix here, because it's not being called immediately, only on authentication events - also I don't want to put it here because my understanding is that this method is called frequently depending on events.
I also found that some people were executing it on a different thread with something along the lines of:
dispatch_async(dispatch_get_main_queue(), {
self.performSegueWithIdentifier(self.successfulSignUp, sender: nil)
})
But what I am thinking is that, the "register" view controller that is triggering the segue, is not the top most view controller and that's why the error is occurring. Also, I read that a solution is to embed a UINavigationController, if this is the solution...does anyone know any resources on how to do this? If there is a different solution, what would it be? Any help would be great! Cheers.
EDIT:
My Storyboard looks like this:
The entry point is to the UITabBarController, and in the AppDelegate.swift I check to see if the user is logged in by checking a boolean value in the user defaults, if they aren't logged in, I change the rootController to the LoginViewController, in which the user can login, and it will segue to the UITabBarController, or they can register, and on successful registration, it will segue to the UITabBarController - it is here that I'm getting the warning
I have not figured out why, and once I do I will update the answer, but to resolve this i put the line self.performSegueWithIdentifier(self.LoginToFeed, sender: nil)
into a seperate function
func callSegue() {
self.performSegueWithIdentifier(self.LoginToFeed, sender: nil)
}
and call the function from the block and the warning no longer appears

Login page goes to main UITabBar view even with incorrect login info

Ok so I'm linked to a server and I know for a fact that I have all the code there correct. But something seems to be wrong with this code:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString* responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
/*Succesful Login*/
if([responseString isEqualToString:#"success"]){
[self performSegueWithIdentifier:#"LoginHomeToMain" sender:self];
NSLog(#"Succesful login.");
}
/*Deactivated Account*/
else if([responseString isEqualToString:#"disabled account"]){
}
/*Incorrect username and password.*/
else if([responseString isEqualToString:#"incorrect"]){
NSLog(#"Incorrect username and (or) password.");
}
/*GET request*/
else if([responseString isEqualToString:#"get"]){
}
}
So I think you can ignore the 2nd and 4th else if's but here's the problem:
When I try a user and pass that I know does not exist in the server, it still segues me to the main tab bar view. Am I supposed to reload the login page's view for the "incorrect" logic, or is my segue wrong? P.S. The segue is simply linked from a login button to the main tab bar view, and is modal.
Also, I keep getting this error message in the debugger:
"Unbalanced calls to begin/end appearance transitions for ."
Any help is greatly appreciated, Thanks!
If you want to perform a segue conditionally, you need to link the segue from the origin view controller to the destination view controller.
Do not link the segue from a UI element such as a button. (if you want to only perform the segue conditionally, that is)
Changing your segue's origin connection from the button to the view controller will fix your problem without changing a line of code.
Personally, I've just started always linking from view controller. It's easy enough to write the one line of code to perform the segue, and you never know when you'll have to write some sort of conditional logic around it later. So it's probably good to just get into the habit of doing it this way.

How to send data to second viewcontroller from populated html form?

I have been working on this problem for a few days, and it´s driving me totally crazy. I´m using Storyboards.
I have viewcontroller1 with a webview. The webview populates a html login form, through a textField (username) another textField (password) and then a button (submit) which work perfect: once pushing the button, the html form is populated with my textFields input. Once the form is filled and submitted, I want the user to be sent to a new viewcontroller (viewController2) which has a webview, that I want to populate with some of the stuff from the website. I cant, for the world, find out how to send the user to the next view.
Another detail is that the login html form has a user type select. Hence, the adress the submit button sends me can be different. If its a teacher filling out the form the returned address is going to be something like "myschool/jsp/teacher/right_teacher_lesson_status.jsp" and if its a student something else, and if its from another school, something else.
This is the code Ive tried to send the user to the next view containing UIwebview2:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:
(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeFormSubmitted) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
ViewController2 *vc = [storyboard instantiateViewControllerWithIdentifier:#"webview2"];
NSLog(#"Allow navigation to request = %#", request);
[self.navigationController pushViewController:vc animated:YES];
return YES;
}
The NSLog tells me
"Allow navigation to request: /myschool/jsp/teacher/right_teacher_lesson_status.jsp"
So if I tell the webview2 the address it will surely pick it up. But since I cant know which school or usertype it is, I want the webview2 to recieve that from the above code. Please help me. How can i pass the info to the next view?
I recommend creating a view with two text fields and a picker menu. This is much nicer than a a web view. If you cannot display the resulting content in a pretty way, you can use a web view, but you should optimize the user experience with custom views whenever possible.
Once you have UIKit elements to capture the data, passing it to the next view controller will be trivial.
That being said:
Maybe this is really a problem of the HTML coding. Your "Submit" button could include the arguments in the URL (GET instead of POST). You can then simply pass the URL on to your next view controller.
vc2.urlToLoad = request;
As you are not showing the URL, the get variables will not be exposed to the user anyway. As your server can interpret the arguments, you do not even have to parse them in the second view controller (although you could if you need them for other purposes).

iCloud enabled - Stop the open file displaying on application launch?

I've just added iCloud support to an app that I am working on. Its working great, except that when I open the application without a document in focus the iCloud open file dialog appears and I don't want it to!
In my app delegate I have:
- (BOOL) applicationShouldOpenUntitledFile:(NSApplication *)sender
{
[mainWindowController.window makeKeyAndOrderFront:self];
return NO;
}
Which I use to show my own custom window. However now, both the iCloud open file dialog and my own dialog are displayed. Any ideas on how I can get rid of the iCloud dialog?
https://developer.apple.com/library/prerelease/content/releasenotes/AppKit/RN-AppKitOlderNotes/index.html
NSDocument Support for iCloud
In 10.8, NSDocument-based applications with a ubiquity-container-identifiers entitlement gain new functionality and UI to facilitate iCloud document management.
When iCloud is enabled and an application is first launched or re-activated and no windows are visible or being restored, instead of creating a new Untitled document, NSDocumentController will display a non-modal open panel showing the user's iCloud library.
...
Applications that do not wish to use these features for any or all of their NSDocument subclasses can override +[NSDocument usesUbiquitousStorage] and return NO. If all of the application's declared NSDocument subclasses return NO from this method, then NSDocumentController will never show the new non-modal open panel.
So if you can give up using the features listed in this release note, return NO at +[NSDocument usesUbiquitousStorage].
I confirmed you can still open/save your file into iCloud storage from the normal dialog.
Putting below codes in your App Delegate lets you bypass that iCloud pop up New Document screen. Tested for High Sierra.
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
// Schedule "Checking whether document exists." into next UI Loop.
// Because document is not restored yet.
// So we don't know what do we have to create new one.
// Opened document can be identified here. (double click document file)
NSInvocationOperation* op = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(openNewDocumentIfNeeded) object:nil];
[[NSOperationQueue mainQueue] addOperation: op];
}
-(void)openNewDocumentIfNeeded
{
NSUInteger documentCount = [[[NSDocumentController sharedDocumentController] documents]count];
// Open an untitled document what if there is no document. (restored, opened).
if(documentCount == 0){
[[NSDocumentController sharedDocumentController]openUntitledDocumentAndDisplay:YES error: nil];
}
}
- (BOOL) applicationShouldOpenUntitledFile:(NSApplication *)sender
{
[mainWindowController.window makeKeyAndOrderFront:self];
return NO;
}
This part is correct. I've just tested it.
Just make sure your that this class is really your app delegate.
Make a new class called prefixAppDelegate
In your MainMenu.xib, drag a new object to the side and set it's custom class to the app delegate class
Right click Application and drag from Delegate down to your app delegate object.
Now just paste the code above into your app delegate class
If that still doesn't help, try logging something in applicationShouldOpenUntitledFile:.
Also, I recommend not to set [mainWindowController.window makeKeyAndOrderFront:self]; in this method. You should rather use the app delegate method applicationDidFinishLaunching: method.
My observation and fix:
[applicationShouldOpenUntitledFile:] won't be executed except you remove Key NSDocumentClass from *-info.plist. But this is harmful if your app is document based application, it won't open the document type you linked.
My fix is open my customised window directly in -(void)applicationWillFinishLaunching:(NSNotification *)notification method (Application delegate)
ETDocumentWindowController *windowController = (ETDocumentWindowController*)get your own window controller here...;
[windowController.window makeKeyAndOrderFront:nil];
I thought I would share my solution to this issue as I see others still looking for an answer. Its not a great solution but it does the trick.
Subclass NSDocumentController and add the following:
+ (void) setCanOpenUntitledDocument: (BOOL) _canOpenUntitledDocument
{
canOpenUntitledDocument = _canOpenUntitledDocument;
} // End of setCanOpenUntitledDocument:
- (void) openDocument: (id) sender
{
// With iCloud enabled, the app keeps trying to run openDocument: on first launch (before apphasfinishedlaunching gets set.
// This method lets us check and see if the app has finished launching or not. If we try to open a document before
// its finished, then don't let it.
if(!canOpenUntitledDocument)
{
return;
} // End of appHasFinishedLaunching not set
[super openDocument: sender];
} // End of openDocument:
Add the following to your app delegate:
- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{
// Finished launching. Let us open untitled documents.
[SQLProDocumentController setCanOpenUntitledDocument: true];
...
}
And the reasoning -- By setting a breakpoint in openDocument I've found that its called before applicationDidFinishLaunching, applicationShouldOpenUntitledFile or applicationShouldHandleReopen:hasVisibleWindows: get called, meaning adding those methods is useless. Again, it's not great code but it works and does the trick. (None of the other solutions have worked for me).
I ran into a similar problem -- it turned out that in my case, I had to remove the NSDocumentClass key and value from my Info.plist in the CFBundleDocumentTypes array. Only then would the applicationShouldOpenUntitledFile: method get called and thus allow me to prevent the iCloud/Document window from opening.

customize an action of iphone home button to submit score in gamecenter

i have a button in my app a button that submit score to gamecenter and works.
this is the code:
-(void)subScore{
GKScore *scoreRepoter = [[[GKScore alloc] initWithCategory:#"123456"] autorelease];
scoreRepoter.value=100;
[scoreRepoter reportScoreWithCompletionHandler:^(NSError *error) {
if (error!=nil) {
NSLog(#"errr submitting");
}else
NSLog(#"ok!");
}];
now i'd like to submit score before app is closed with home button.
i thought to customize an action of home button (if it is possible)
or perhaps i make the same line of code in viewDidUload...or something like that...
will i be sure that that action will be performed before unloading the app?
i should make that code in dealloc method?
thanks
You can't customize behaviour of Home button directly, but iOS provides some methods in your application's delegate, by which you can control lifecycle of the application.
Method called right before the application goes to background is applicationWillResignActive: in your application's delegate (usually this method is located in AppDelegate.m file).
I think you can get needed effect by calling your method like that:
- (void)applicationWillResignActive:(UIApplication *)application {
[mygame subScore];
}
Also please note that iOS has time limit of execution for this method: you must do all saving-the-game work in less that five seconds or your application will be killed.