I currently have a launch screen storyboard with an image set up as my loading screen.
I'm used to doing everything programmatically, and storyboarding is throwing me for a ride. I have a boolean set up to determine if this is the first time the app is being launched. If so, I want to have one view controller that I set as the root of my navigation controller. If it isn't the first launch, I want to set a different root view controller to my nav controller.
I've clicked and dragged a navigation controller onto my storyboard. I just don't know what to do with it now. I have a viewController set up with an image that I'd like to be the first thing seen if it is the first time the app is being launched. However, I'm not sure what is automatically initialized using storyboard, so conceptually I'm not sure how to set up my app.
Since I have to handle this condition programmatically, do I even need to create a nav controller in storyboard, or would that be redundant?
My parent VC (viewController) is just an image with a label overlaid on top. Building with the below code in my appDelegate.h file correctly shows my launch screen storyboard, but then fades to a black screen.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
firstLaunch = YES;
if (firstLaunch == YES) {
ViewController *launchScreen = [ViewController new];
UINavigationController * navControl = [[UINavigationController alloc]initWithRootViewController:launchScreen];
self.window.rootViewController = navControl;
firstLaunch = NO;
}
else {
// create instance of other view controller and set as root of navigation controller
}
return YES;
}
Any ideas as to why my app is just navigating to a black screen?
Make sure to use
[window makeKeyAndVisible];
I usually do this in my application like so:
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.navController = [[UINavigationController alloc]init];
self.window.rootViewController = self.mainViewController;
[self.window makeKeyAndVisible];
In addition, your code will always say it's the user's first time. I would recommend using NSUserDefaults as such:
BOOL hasLaunchedBefore = [[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedBefore"];
if (!hasLaunchedBefore) {
//All your first launch UI stuff
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"HasLaunchedBefore"];
[[NSUserDefaults standardUserDefaults] synchronize];
} else {
//Not first launch
}
Related
I am currently having an issue with my login flow. The logic is all there but the animation from the login view to the main view is weird.
Heres a side note: The main view is a UITabBarController with 5 UINavigationControllers and the login view is just a UIViewController
So first I determine in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions , whether the current user is already logged in (I am using Parse to handle that), if their not then set the UIWindows rootViewController to be the Login controller. Else, set the UIWindows rootViewController to be the main view.
This works fine... Assume that the user is logged in on app launch. Okay cool, it shows the main View. I then sign out and everything is working as expected. I switch the UIWindows rootViewController by running this method:
- (void)showLogin {
self.activity = nil;
self.chat = nil;
self.create = nil;
self.notification = nil;
self.more = nil;
self.loginViewController = nil;
if (!self.loginViewController) { // this check is redundant i feel lol
self.loginViewController = [[PHLoginRegisterViewController alloc] init];
}
[UIView transitionWithView:self.window
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.window.rootViewController = self.loginViewController;
} completion:^(BOOL finished) {
self.activity = [[PHActivityViewController alloc] init];
self.chat = [[PHGlobalChatViewController alloc] init];
self.notification = [[PHNotificationsViewController alloc] init];
self.more = [[PHMoreViewController alloc] init];
[self.tabBarController setViewControllers:#[[[UINavigationController alloc] initWithRootViewController:self.activity],
[[UINavigationController alloc] initWithRootViewController:self.chat],
[[UINavigationController alloc] init],
[[UINavigationController alloc] initWithRootViewController:self.notification],
[[UINavigationController alloc] initWithRootViewController:self.more]]];
[self.tabBarController setSelectedIndex:0];
}];
}
Okay, so the login view is now shown with a nice transition... Then when I attempt to login, everything works fine, but when the transition is happening login->main, the entire main view (except for the position of the UITabBar of the UITabBarController) is basically set -20px for a brief moment, then gets reset to its expected position... Heres two screenshots of when the login is transitioning to the main view, then when it is done transitioning...
I've tested this exact implementation without a UITabBarController and it doesn't do this. This sorta GLITCH issue. Any ideas that anyone can pass along?
I want to know if its possible to do this login->main and main->login transition without having to implement the main's viewDidAppear or viewWillAppear methods.
For example, in the main views viewDidAppear,
if (user is logged in)
[self presentViewController:login animated:NO]
This causes the main screen to be seen for a brief moment before the loginview is shown, which I don't want.
I think the problem is that before your transition is completed, the auto layout process is not triggered, so your view is the position it is in your xib design.
I have an idea to work around this, whenever the app is launching, you use you main view controller as the root view controller, and if you need login, in main view controller's viewDidLoad, you load the login view controller and add it's view as the top subview of main view controller, this will solve problem that presentation has.
The only drawback is that your login view need a opaque background.
Would it be possible to show a different splash screen (launch image) on the first load of an application?
For example the first load would have a text ("Please wait while we setup your app..."), while the following loads would have another splash screen (no text for example).
No this is not possible, since you can not change the launch image because the main bundle is read only.
You could however present a view controller just after your application is started and have the text in this view.
Just return as soon as possible from the application:didFinishLaunchingWithOptions: and only load the view controller with the text in this method.
Then start doing what ever your app need to do, and dismiss the view controller when done.
No, the default image is there at a time when your app isn't yet executing any of your code (well mostly). The default screen is very quick to load on most modern devices, so instead of a splash screen, just use a normal view/view controller for setup.
As both previous answer of #nevan king and #rckoenes, you can present a view controller and have the text or image or whatever in this custom splashscreen.
Add a method on didFinishLaunchingWithOptions to present this view :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Create View Controller
RootViewController *rootViewController = [[RootViewController alloc] init];
// Create Navigation Controller
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
// SplashScreen
[self displaySplashscreen];
return YES;
}
Create displaySplashscreen:
#pragma mark - SplashScreen Methods
- (void)displaySplashscreen
{
// Create View
self.splashscreenViewController = [[SplashscreenViewController alloc] init];
// Display Splashscreen
[_window addSubview:_splashscreenViewController.view];
// Dismiss Splashscreen
[self performSelector:#selector(dismissSplashscreen) withObject:nil afterDelay:3.0f];
}
Create your SplashscreenViewController and build what you need in this view controller.
When my app first loads, I set the rootViewController property of my UIWindow to controllerA.
Sometime during my app, I choose to change the rootViewController to controllerB.
The issue is that sometimes when I do a flip transition in controllerB, I see controllerA's view behind it. For some reason that view isn't getting removed. Whats even more worrying is that after setting the rootViewController to controllerB, controllerA's dealloc method never gets fired.
I've tried removing the subviews of UIWindow manually before switching to controllerB, that solves the issue of seeing controllerA's views in the background but controllerA's dealloc still never gets called. Whats going on here????
Apples docs say:
The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller’s view as the content view of the window. If the window has an existing view hierarchy, the old views are removed before the new ones are installed.
UPDATE
Here's the code of my AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self showControllerA];
[self.window makeKeyAndVisible];
return YES;
}
- (void)showControllerA
{
ControllerA* a = [ControllerA new];
self.window.rootViewController = a;
}
- (void) showControllerB {
ControllerB* b = [ControllerB new];
self.window.rootViewController = b;
}
It turns out there are two separate issues. 1) I had a retain cycle in Controller A so it was never getting dealloc'd. Secondly, in order to change the root view controller you must remove the windows subviews first (even though the docs suggest otherwise)
The problem could be in your implementation of ControllerA or ControllerB, they may retain 'self' in the code so ARC cant automatically dealloc you ViewController. Can you post you ControllerA and ControllerB implementation.
var loginNavigationController: OnBoardViewController?{
willSet{
if newValue == nil {
loginNavigationController?.view.removeFromSuperview()
}
}
}
loginNavigationController = nil
It's apple's bug, we assume ViewControllerA as the current rootViewController:
// ViewControllerA.m
- (void)buttonClick {
[self dismissViewControllerAnimated:YES completion:^{
// [((AppDelegate *)[[UIApplication sharedApplication] delegate]) resetRoot]; // OK
}];
[((AppDelegate *)[[UIApplication sharedApplication] delegate]) resetRoot]; // ViewControllerA's view will not dealloc
}
// AppDelegate.m
- (void)resetRoot {
ViewControllerB *controller = [[ViewControllerB alloc] init];
self.window.rootViewController = controller;
}
If reset window's rootViewController as this code, the ViewControllerA's view will never dealloc.
An even simpler solution is to set the backgroundColor of your new window to .white or any color. The default is nil, which results in a transparent background. That is why the older window (on top of which the new one is made visible) is being seen through.
I have a problem I can't figure out, I have made an application which uses UIsplitview inside a tab bar. I have been implementing the different tabs however now when I am working on the first tab - the UIsplitview is not aligned in landscape mode. Do you guys have any suggestions - if I start it in portrait and go to landscape, then there's no problem at all.
Update:
I dont do any init with frames anywhere, and I have checked the sizes etc. in IB. The following shows how I add the uisplitview controller in the app delegate. It has been done this way because I wanted a splitview in a tabbar controller. When i have added the spilview I just set the master and detail view in IB. A bit of mystery.
if (index == 2) {
detailViewController = [[DetailUserCreatorViewController alloc] initWithNibName:#"DetailUserCreatorView" bundle:nil];
userContent=[[UserContentForPopViewController alloc]init];
userContent.userDetails=detailViewController;
detailViewController.delegate=userContent;
//rootViewController.navigationItem.title = #"List";
UINavigationController *nav = [[[UINavigationController alloc] initWithRootViewController:userContent] autorelease];
splitViewController = [[UISplitViewController alloc] init];
splitViewController.tabBarItem = controller.tabBarItem;
splitViewController.viewControllers = [NSArray arrayWithObjects:nav, detailViewController, nil];
splitViewController.delegate = detailViewController;
[controllers replaceObjectAtIndex:index withObject:splitViewController];
}
Update: I tried to set the selected tab in application didfinishlaunch in the app delegate - self.tabBarController.selectedIndex = 0; and this made the tab start at the correct placement. However it does not seem to be a proper solution.
Some pointers...splitViewController needs to be added as a subview of window:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window addSubview:splitViewController.view];
[window makeKeyAndVisible];
return YES;
}
The following code is incorrect. You should not assign a viewController to a delegate.
splitViewController.delegate = detailViewController;
You will also not require this line of code:
[controllers replaceObjectAtIndex:index withObject:splitViewController];
The following line handles that part of assigning delegates.
splitViewController.viewControllers = [NSArray arrayWithObjects:nav, detailViewController, nil];
Also, if you can upload your code, I'll try to correct it and post back the reason and corrected code...
I need to use an MGSplitViewController because of it's ability to show the master view controller in the portrait mode. However, before displaying my split view, I need to display a login screen. Unfortunately I am unable to pop the view controller in fullscreen at startup because of some other methods that I have called! Below, is my app delegate and detail view controller codes. Please note, that the selector methods prevent me from opening a modal!
AppDelegate.h was constructed using MGSplitViewControllerAppDelegate.h
// RandomStringAppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch.
// Set the split view controller as the window's root view controller and display.
//self.window.rootViewController = self.splitViewController;
// Add the split view controller's view to the window and display.
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:#"YES" forKey:#"FirstRun"];
[window addSubview:splitViewController.view];
[window makeKeyAndVisible];
[rootViewController performSelector:#selector(selectFirstRow) withObject:nil afterDelay:0];
[splitViewController performSelector:#selector(toggleMasterView:) withObject:nil afterDelay:0];
[detailViewController performSelector:#selector(configureView) withObject:nil afterDelay:0];
//[self.window makeKeyAndVisible];
return YES;
}
Everything Else is Standard!
Unfortunately, I cannot pop the modal here because it crashes on me!
You could derive a class from MGSplitViewController and handle your things in viewDidLoad or viewWillAppear: in that class. So you could track your prefs key "FirstRun" and if it's set to "YES" you hide your splitview while you start your modal in viewDidLoad. I think this could do the job. btw you're missing a [prefs synchronize] in your code above, so you won't have the key written back.