First question here!
I have two storyboards in an app, a login and a main. After creating an account, the user proceeds to the MainStoryboard and all is well. But there are instances where the user may end up back in the LoginStoryboard to edit thier account info or just to log out, and then back to the MainStoryboard if need be.
Only in iOS 6 (in iOS 7, I have not been able to reproduce), the LoginStoryboard will sometimes lock up or quit responding to user interaction, but UIButtons usually work. Sometimes animations and the frames of subviews do not look right.
Repro:
LoginStoryboard > //login
MainStoryboard > //view content
LoginStoryboard > //update user info, for example
MainStoryboard > //back to content
LoginStoryboard > //back to user info
The last step is where things go wrong and the user is stuck in the LoginStoryboard as nothing will respond to interactions.
Here is an example of the code I'm using to load a new storyboard...
- (void)navigateToHomeScreen
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"LoginStoryboard" bundle:nil];
UIViewController *initialViewController = [storyboard instantiateInitialViewController];
initialViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:initialViewController animated:YES completion:nil];
}
If I set a breakpoint on one of the didReceiveMemoryWarning methods after navigating back and forth between the LoginStoryboard and then simulate a memory warning on the simulator after the subviews have quit responding to the user, the breakpoint is hit 2-3 times on different instances of the same view controller.
So I think that I am either making new instances of these view controllers when I should be reusing old ones or the old instances are never getting dealloc-ed.
Thank you for any suggestions!
Related
I have a problem with the UINavigationController. It about a client or serverside logout.
The idea for serverside logout is this, every 15 seconds a function is called that checks if the App is still logged in. If that is not the case then jump to the LoginViewController.
The Logout can also happen from the App itself. It executs simular code.
There are three relevant Controllers, LoginViewController is where we want to end up, SignOutController is where the 'Sign out'-Button is located and MainViewController.
Here are the relevant code parts.
First, the UINavigationController gets allcated like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
splashScreenViewController_ = [[SplashScreenViewController alloc] initWithNibName:#"SplashScreenViewController" bundle:nil];
uiNav_ = [[UINavigationController alloc] initWithRootViewController:splashScreenViewController_];
uiNav_.navigationBar.barStyle = UIBarStyleBlackTranslucent;
uiNav_.navigationBarHidden = YES;
[window_ setRootViewController:uiNav_];
[window_ makeKeyAndVisible];
return YES;
}
When the 'Sign out'-Button is pressed or the App figures out that the Server has forced a logout, this code is executed (same code, diffrent functions):
LoginViewController *loginView = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:nil];
[self.navigationController pushViewController:loginView animated:YES];
[loginView release];
If its a Serverside Logout it just reloads the MainViewController, every 15 seconds you see the animation of MainViewController sliding in. Its goes in a cycle from there, every 15 seconds it reloads.
If you click the 'Sign out'-Button it jumps to MainViewController instead of LoginViewController and starts the same cycle discribed above.
P.S. I have checked if any importend variable is nil, and I have checked that initalisation code is actually executed.
Edit: I think I did not mention that. My app works 99% of the time. Just one in a while this happen that the Sign-out button does not work and I start this cycle. Normally it works fine.
For the 15 second cycle where a new LoginViewController slides in it just seems you are not stopping to check if the app is logged in after realizing it wasn't. You should have some sort of boolean to store that and cancel the timer or whatever you use.
I don't understand what you say happens when you press the logout button, but I don't think you are making a good user interface.
I suggest you start the application by adding to the navigation controller the loginViewController as root. Then you add without animation the one you want to start with (for example MainViewController). Whenever the application is logged out of the service you pop the view controllers until the first one, which sould be the login one.
You have the method popToRootViewControllerAnimated: for that.
If you want to preserve the splash screen you can set it as the root view controller of the app, and chenge it to the uiNavigationController when you have finished loading.
The timer won't stop automatically just because you've pushed a view on top of another. It will be there until the controller it started on is released, which will only happen after it has been removed from the stack.
Also, you don't need to push MainViewController onto the stack after every check, you'll end up with multiple instances of it, each on top of another.
Also, without really knowing much about the architecture of the app, it would seem like a good idea to make LoginViewController modal, they really can't do anything if they've not logged in right? A modally presented viewcontroller wouldn't be affected by the navigation stack, and would also retain the users navigation stack much easier than having to manually push/pop controllers.
I've just started on iOS programming and so far the tutorials and answers I found here have been a great help to move forward. However, this particular problem has been bumming me all night and I can't find an answer that "feels right".
I'm writing an application that connects to a remote service and the users need to sign in before they can use it. When they start using the application, their first view should be the sign in dialog; when they've authenticated before, they immediately see the overview page.
The project uses story boards - which I think is a great feature - so most of the code that selects and loads the root view controller is already taken care of. I thought the best place to add my logic is the application:didFinishLaunchingWithOptions: method of the AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
// select my root view controller here based on credentials present or not
return YES;
}
But this brought up two questions:
Inside this particular delegate method, the root view controller has already been selected (and loaded?) based on the story board. Could I move to an earlier spot in the loading process to override the first view controller selection or would that needlessly complicate matters?
To override the first view controller I need a reference to the story board, but I couldn't find a better way than to use the storyboardWithName:bundle: constructor of UIStoryboard. That feels wrong, the application should already have a reference to the story board, but how can I access it?
Update
I worked out the second issue I was having, as I found my answer here:
UIStoryboard: What's the Correct Way to Get the Active Storyboard?
NSBundle *bundle = [NSBundle mainBundle];
NSString *sbFile = [bundle objectForInfoDictionaryKey:#"UIMainStoryboardFile"];
UIStoryboard *sb = [UIStoryboard storyboardWithName:sbFile bundle:bundle];
The above will create a new story board instance; to get the active instance, it's a whole lot simpler:
UIStoryboard *sb = [[self.window rootViewController] storyboard];
In the story board file itself you have to set an identifier for the view you wish to load, e.g. LoginDialog. Afterwards you instantiate the view like this:
LoginViewController *login = [sb instantiateViewControllerWithIdentifier:#"LoginDialog"];
[self.window setRootViewController:login];
Within another view controller, the following suffices:
UIStoryboard *sb = self.storyboard;
LoginViewController *login = [sb instantiateViewControllerWithIdentifier:#"LoginDialog"];
[self presentViewController:login animated:NO completion:nil];
You can just reset the root view controller of the window
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
if(your_condition) {
UIViewController *newRoot = [your implementation];
self.window.rootViewController = newRoot;
}
return YES;
}
This is worked for me, Xcode5.0.1
I have a similar scenario as yours. My application uses a UINavigationController as the root view controller. If the user is logged in, I want to present him/her with NotLoggedInViewController, while if it's logged in I want to show the LoggedInViewController.
In a storyboard a UINavigationController can only have one child, so you have to be able to programmatically assign another root view controller to it.
I start by creating a custom navigation controller class, let's name it MyNavigationController. In the storyboard I assign this custom class to the navigation controller object.
Still in the storyboard, I then model both view controllers, and connect one of them to the navigation controller object. Since I need to be able to access them from my code later on, I assign each of them an identifier using the XCode inspector on the right. These identifiers which can be arbitrary strings, but to things simple I just use the class names.
Finally I then implement the viewDidLoad method on MyNavigationController class:
BOOL isLoggedIn = ...;
- (void)viewDidLoad {
id rootController;
if (isLoggedIn) {
rootController = [self.storyboard instantiateViewControllerWithIdentifier:#"LoggedInViewController"];
} else {
rootController = [self.storyboard instantiateViewControllerWithIdentifier:#"NotLoggedInViewController"];
}
self.viewControllers = [NSArray arrayWithObjects:rootController, nil];
}
I had hardly used storyboard & probably this is not the exact answer to your question. But I will suggest you some way what I did in my project created without using a storyboard.
In didFinishLaunchingWithOptions AuthenticationViewController is the first view loaded. It asks login credentials. Once entered it will enter the actual ViewControllers(viz. TabBar &all..) used by project.
Interesting feature added to project is, when you enter credentials I popped up an UIAleretView that asks user to choose one of the three options.
Save Credentials without passcode
Save Credentials with passcode
Dont save credentials
Here pass code is nothing but 4digit number entered by user. Whenever he wants to 'Save Credentials with passcode', I pushViewController that shows NumberPad instad of default keyboard & popviewController when it finishes entering of pin. If user 'Dont save credentials' & later on while playing the app wants to go for other authentication options then I added the last tab of TabBarController as 'Settings' tab inside which I allow user to choose one of the Authentication options, popped as UIAlertView in the beginning of app start after login.
Dont forget to Save credentials in keychain
In a nutshell,
AuthenticationViewController-> check if login credentials are stored in keychain
1.1. If not stored(i.e. 3. Dont save credentials)-> then show Login page.
1.2. If credentials are saved in keychain-> extract them & see if it is tied with passcode.
1.2.1. If it is tied with passcode(i.e. 2. Save Credentials with passcode
)-> then show passcode page.
1.2.2. If it is not tied (1. Save Credentials without passcode)-> then show/load you project's TabBarController hierarchy or other stuff. here actually your app start.
With the main storyboard already loaded, it's just a matter of finding its reference so that I can instantiate another root view controller:
UIStoryboard *mainStoryboard = self.window.rootViewController.storyboard;
self.window.rootViewController = [mainStoryboard
instantiateViewControllerWithIdentifier:#"view-controller-id"];
I have a screen in which a user can choose a set of meals - once the meals have been chosen the application fetches results form a database and displays a list of them. Now, I would like to implement a condition to decide whether the next screen should be loaded or not - ie. if there's no internet connection then show an alert and don't display the next screen etc.
I've implemented a system to check whether there is an internet connection or not but I'm not sure how and where to decide of the next screen should be loaded. Any ideas?
Thanks,
1.5 other options:
If you're willing to split stories and nibs, just load up a nib when you want/need to.
If you want to stick to stories exclusively, just load up another story when you need to. Same thing as loading a nib:
UIStoryboard *otherStoryboard = [UIStoryboard storyboardWithName:#"OtherStory" bundle:nil];
UIViewController *otherController = [otherStoryboard instantiateInitialViewController];
[self.navigationController pushViewController: otherController animated:YES];
Once you know in your code whether you want to display the next screen or not, can you not just add an if statement that either loads the next screen or displays a warning that there is no connection?
if (hasConnection) {
// Show next screen
} else {
// Show warning
}
You supposedly have an action that is being fired when the user selects some meals, haven't you? In this action you'd call [UINavigationController pushViewController:nextViewController animated:YES] or something like this. Put this function call into the condition of your preference, and show a popup otherwise.
I solved this issue using the answers from:
Prevent segue in prepareForSegue method? by linking the segue to my main view controller, then attaching an IBAction to the button that was originally the segue initiator and performing the logic in that method. If it all cleared then I call [self performSegueWithIdentifier:#"results" sender:self];
My app downloads data from internet when it starts, during the splash screen.
It does the same when, from the background, it enters in foreground (when the user open the app from the background).
When the app is open, the user can push some views in order to read the informations downloaded.
I want that when the app is open from the background state, the viewControllers are popped until the first view is showed...
I want to do something like this in my AppDelegate:
while ([self.view isNotMainView]) //of course this method doesn't exists
{ [self.navigationController popViewControllerAnimated:NO]; }
is it possible?
Thanks
Just use:
[self.navigationController popToRootViewControllerAnimated:NO];
Hope that Helps!
hope Following link will help...
How are people popping their UINavigationController stacks under a UITabBarController?
You can keep a reference or Current navigation controller in your appdelegate OR you can write this in you viewDidUnload OR viewWillDisapper for popping navigation to root when application goes to background.
You could just compare the view against your main view if you have a reference hanging around:
while (self.view != myMainView)
etc. (Assuming that self.view is the correct reference as well.)
I will like my application that always starts as the first time I open it. I have several view controllers and when I exit my application and I open it again I see the view controller where I left of. Maybe I have to call the method applicationWillTerminate method.
I use this code to open a new view:
UIViewController *control = [[SomeViewController alloc] initWithNibName:#"SomeViewController"
bundle:nil];
UINavigationController *navControl = [[UINavigationController alloc]
initWithRootViewController:control];
[self presentModalViewController:navControl animated:NO];
[navControl setNavigationBarHidden:YES];
[control release];
[navControl release];
this code works great when linking it to buttons. But when I place that code in the applicationDidBecomeActive method it does not work.
The easiest way is to set UIApplicationExitsOnSuspend in Info.plist.
That really isn't the expected behaviour, though. Users expect to see the app "where they left off", especially if they've only briefly left the app (e.g. because they got a phone call).
Your code snippet adds a view controller, but is unlikely to work since your app delegate is not a UIViewController. It also doesn't do anything about removing the old view controllers.
EDIT: If all you need to do is display a splash screen (or something), then it's something like this:
In -applicationDidEnterBackground:, add a "splash screen" view (not a view controller) to self.window. (iOS takes a "screenshot" after you return from -applicationDidEnterBackground: and uses this in the app-switch animation; you want this to be what the user sees when switching back to your app)
In -applicationWillEnterForeground:, do whatever animations you want and eventually remove the view from the window (call -removeFromSuperview).
EDIT 2: The same will work in -applicationWillResignActive:/-applicationWillBecomeActive:, except this happens on a sleep/wake event, which might not be what you want...
I'd avoid using view controllers for this, because trying to shoehorn a view controller in the view controller hierarchy is likely to be problematic (for example, you have to figure out which VC to present it from, and you have to do the "right thing" if the user backgrounds your app while the VC is on screen, and...)
The reason it doesn't work in applicationDidBecomeActive is that method is only sent to the Application delegate, which doesn't know about presentModalViewController.
I suggest instead that in your appDelegate, implement applicationWillEnterForeground:, which should restore the state to a newly launched application (equivalent to what the state is at the end of application:didFinishLaunchingWithOptions: ).
OR...(edits)
If you just want a certain viewController to run (which is still loaded, right?)...For example, if you have a tab controller and just want to go to the root of the first view controller, put the following code into applicationWillEnterForeground:
UITabBarController * myTabBar = self.tabBarController;
myTabBar.selectedIndex = 0;
[[myTabBar.viewControllers objectAtIndex:0] popToRootViewControllerAnimated:NO];
Temporary solution:
I made my application crash on applicationWillResignActive method and it works. My application needs to run an animation when I launch it. But this works because next time the application runs it starts like the first time I opened it.