My project was first created with a UITabBarController as the first view to appear, then I needed to add a custom splash screen that appears for 3 sec so I used a new UIViewController which appears before the UITabBarController and I set this custom splash screen as the first view to appear. However, after I did that change. At the moment my splash screen goes to the UITabBarController im receiving this error.
Warning: Attempt to present UITabBarController: 0x1cdcfe30 on SplashViewController: 0x1cdc55e0 whose view is not in the window hierarchy!
Im performing the change of view in my SplashViewController in this way:
#import "SplashViewController.h"
#interface SplashViewController ()
#end
#implementation SplashViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(changeView) userInfo:nil repeats:YES];
}
-(void)changeView{
[self performSegueWithIdentifier:#"splash" sender:self];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
By the way im using storyboards this is a strange error that appears in the console all the time after I added that splash screen and I cant figure out how to get rid of it.
Give Storyboard id "TabBarViewController" for TabBarController in Identity Inspector(Storyboard)
And implement changeView as-
-(void)changeView
{
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
TabBarViewController *tabBarViewController = [storyBoard instantiateViewControllerWithIdentifier:#"TabBarViewController"];
[[[[UIApplication sharedApplication] delegate] window] setRootViewController:tabBarViewController];
}
There's no need to add another controller for a splash screen. Just add a view (your splash screen view) on top of the view of the controller in the first tab. That view will appear at app start up, then after whatever delay you want, fade out that view and remove it from its superview.
Related
Ok so I have been testing out my own methods for changing the presented storyboards upon rotation and have been doing so with the -(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation method to be assigning self to another method which runs an if-else statement which checks the orientation and loads the correct storyboard appropriately. The problem I'm having is the checking if it's in portrait mode or the else part of the statement. When I run the app in simulator and rotate the device to landscape, it loads the landscape storyboard like it should. But when I rotate the device back to portrait mode, it doesn't change back to the portrait storyboard. At first I thought the problem was that I wasn't dismissing the view controllers but after doing that, it still didn't work. I realized that it was the else part of the statement that wasn't working. I stuck anNSLog(#"PMComplete") and still after running the app again and rotating to landscape and back, it still didn't show up in the log. Does anyone have any thoughts on what's wrong here? I am operating this on the viewcontroller for the portrait storyboard (assigned the view controller in the portrait storyboard the ViewController class). I also created this app without storyboards and created them from scratch and assigned the portrait one as the one that loads up from the AppDelegate.
Here is my ViewController.m file:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize isLandscape;
#synthesize isPortrait;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewChanged {
UIInterfaceOrientation theOrientation = self.interfaceOrientation;
UIStoryboard *portraitStoryboard;
UIStoryboard *landscapeStoryboard;
portraitStoryboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
landscapeStoryboard = [UIStoryboard storyboardWithName:#"LandscapeStoryboard" bundle:nil];
UIViewController *portraitViewController = [portraitStoryboard instantiateInitialViewController];
UIViewController *landscapeViewController = [landscapeStoryboard instantiateInitialViewController];
if (theOrientation == UIInterfaceOrientationLandscapeLeft ||
theOrientation == UIInterfaceOrientationLandscapeRight) {
[portraitViewController dismissViewControllerAnimated:NO completion:nil];
[self presentViewController:landscapeViewController animated:NO completion:nil];
NSLog(#"LSComplete");
}else{
[landscapeViewController dismissViewControllerAnimated:NO completion:nil];
[self presentViewController:portraitViewController animated:NO completion:nil];
NSLog(#"PMComplete");
}
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[self viewChanged];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I've seen this question in various forms without a clear answer. I'm going to ask and answer here. My app needs to do work on startup... inits, a few network calls, login, etc. I don't want my main view controller working until that's done. What's a good pattern for doing this?
Requirements:
iOS5+, storyboards, ARC.
the main vc's view cannot appear until the startup work is done.
want a choice of transition styles to the main vc when the startup work is done.
want to do as much layout as possible in storyboard.
the code should be cleanly contained somewhere obvious.
EDIT, July 2017
Since the first writing, I've changed my practice to one where I give the start up tasks to their own view controller. In that VC, I check startup conditions, present a "busy" UI if needed, etc. Based on the state at startup, I set the window's root VC.
In addition to solving the OP problem, this approach has the additional benefits of giving better control of the startup UI and UI transitions. Here's how to do it:
In your main storyboard, add a new VC, called LaunchViewController and make it the app's initial vc. Give your app's "real" initial vc an identifier like "AppUI" (identifiers are on the Identity tab in IB).
Identify other vcs that are the starts of main UI flows (e.g. Signup/Login, Tutorial, and so on) and give these descriptive identifiers, too. (Some prefer to keep each flow in it's own storyboard. That's a good practice, IMO).
One other nice optional idea: give your app's launch storyboard's vc an identifier, too (like "LaunchVC"), so that you can grab it and use it's view during startup. This will provide a seamless experience for the user during launch and while you do your startup tasks.
Here's what my LaunchViewController looks like....
#implementation LaunchViewController
- (void)viewDidLoad {
[super viewDidLoad];
// optional, but I really like this:
// cover my view with my launch screen's view for a seamless start
UIStoryboard *storyboard = [self.class storyboardWithKey:#"UILaunchStoryboardName"];
UINavigationController *vc = [storyboard instantiateViewControllerWithIdentifier:#"LaunchVC"];
[self.view addSubview:vc.view];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self hideBusyUI];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self showBusyUI];
// start your startup logic here:
// let's say you need to do a network transaction...
//
[someNetworkCallingObject doSomeNetworkCallCompletion:^(id result, NSError *error) {
if (/* some condition */) [self.class presentUI:#"AppUI"];
else if (/* some condition */) [self.class presentUI:#"LoginUI"];
// etc.
}];
}
#pragma mark - Busy UI
// optional, but maybe you want a spinner or something while getting started
- (void)showBusyUI {
// in one app, I add a spinner on my launch storyboard vc
// give it a tag, and give the logo image a tag, too
// here in animation, I fade out the logo and fade in a spinner
UIImageView *logo = (UIImageView *)[self.view viewWithTag:32];
UIActivityIndicatorView *aiv = (UIActivityIndicatorView *)[self.view viewWithTag:33];
[UIView animateWithDuration:0.5 animations:^{
logo.alpha = 0.0;
aiv.alpha = 1.0;
}];
}
- (void)hideBusyUI {
// an animation that reverses the showBusyUI
}
#pragma mark - Present UI
+ (void)presentUI:(NSString *)identifier {
UIStoryboard *storyboard = [self storyboardWithKey:#"UIMainStoryboardFile"];
UINavigationController *vc = [storyboard instantiateViewControllerWithIdentifier:identifier];
UIWindow *window = [UIApplication sharedApplication].delegate.window;
window.rootViewController = vc;
// another bonus of this approach: any VC transition you like to
// any of the app's main flows
[UIView transitionWithView:window
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:nil];
}
+ (UIStoryboard *)storyboardWithKey:(NSString *)key {
NSBundle *bundle = [NSBundle mainBundle];
NSString *storyboardName = [bundle objectForInfoDictionaryKey:key];
return [UIStoryboard storyboardWithName:storyboardName bundle:bundle];
}
#end
The original answer below, though I prefer my current approach
Let's express the app's readiness to run the main vc with a boolean, something like:
BOOL readyToRun = startupWorkIsDone && userIsLoggedIn;
Create an AppStartupViewController and lay it out in app storyboard.
Don't drag any segue's to it, and don't make it the staring vc, just leave it floating somewhere.
In the vc's attributes inspector in storyboard, set it's identifier as "AppStartupViewController".
In the AppStartupViewController.m, when the readyToRun conditions have been met, it can dismiss itself:
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; // your choice here from UIModalTransitionStyle
[self dismissViewControllerAnimated:YES completion:nil];
Now, whenever the app becomes active, it can check for readiness to run, and present the AppStartupViewController if it's needed.
In AppDelegate.h
- (void)applicationDidBecomeActive:(UIApplication *)application {
BOOL readyToRun = startupWorkIsDone && userIsLoggedIn;
if (!readyToRun) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
AppStartupViewController *startupVC = [storyboard instantiateViewControllerWithIdentifier:#"AppStartupViewController"];
[self.window.rootViewController presentViewController:startupVC animated:NO completion:nil];
// animate = NO because we don't want to see the mainVC's view
}
}
That's mostly the answer, but there is one hitch. Unfortunately the main vc gets loaded (that's okay) and gets a viewWillAppear: message (not okay) before the AppStartupViewController is presented. It means we have to spread a little extra startup logic, like this, in MainViewController.m:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (readyToRun) {
// the view will appear stuff i would have done unconditionally before
}
}
I hope this is helpful.
Another solution when using a navigation controller.
Your navigation controller being the initial view controller, set your main controller as the navigation controller root's view.
Add your loading controller to the storyboard and link with a named segue of style modal.
In your main controller's viewWillAppear trigger the segue (only once per app run).
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(_isFirstRun)
{
_isFirstRun = NO;
[self performSegueWithIdentifier:#"segueLoading" sender:nil];
}
}
It will not work if you trigger the segue in viewDidLoad probably because the navigation controller's animation is not done yet and you will receive an unbalanced calls to begin/end appearance transitions
Is there way to share a UIView between a parent and child UIViewController without any noticeable visual glitches when it's added to the child's view as a subview?
I have a UIViewController and its corresponding UIView, considered as a 'masthead', that will be shared between a parent and child UIViewController. (image something like a stock ticker that will be present somewhere on the screen, across all screens in the app)
When the child UIViewController is created and pushed on to view hierarchy (I'm using them with 'UINavigationController'), what I see is its placeholder background area peaking through before the masthead view is added as a subview.
I thought about creating unique masthead view for each screen but most of the app's views will share this masthead. Thus managing content changes across all of them seemed complicated and I'm trying to take the simpler route by having 1 instance.
AppDelegate work:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Other prep work, including setup of self.window
UIViewController *vc = [[ParentViewController alloc] initWithNibName:nil
bundle:[NSBundle mainBundle]];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = navController;
}
The Parent UIViewController implementation:
#interface ParentViewController : UIViewController {}
#implementation ParentViewController()
- (void)viewDidLoad
{
// The shared controller and its view has already been created and initialized.
// Adding the masthead to my view
[self.view addSubview:self.mastheadController.view];
[super viewDidLoad];
}
- (void)showChildController
{
DetailViewController *detailController = [[DetailViewController alloc] initWithNibName:nil
bundle:[NSBundle mainBundle]
withMastheadController:self.mastheadController];
[self.navigationController pushViewController:detailController animated:YES];
detailController = nil;
}
Here's Child UIViewController implementation:
#interface DetailViewController : UIViewController {}
#implementation DetailViewController()
- (void)willMoveToParentViewController:(UIViewController *)parent
{
// Since this method is invoked before our viewDidLoad and the
// parent's viewWillDisappear, remove shared view from parent view
// stack.
[self.mastheadController.view removeFromSuperview];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Adding the shared masthead controller view to our view
[self.view addSubview:self.mastheadController.view];
}
The reason I'm using willMoveToParentViewController is because I thought that if I wait until 'viewWillDisappear' gets called for the ParentViewController, it was not enough time for the mastheadView to get inserted into the child view hierarchy without any noticeable effects.
This is my understanding about the order in which the view appear/disappear events happen:
child:willMoveToParentViewController (parent will be null upon dismiss)
child:viewDidLoad
parent:viewWillDisappear
child:viewWillAppear
Any help would be greatly appreciated.
I would approach this differently. Why not add the view to navController's view, and then remove it when you don't want to see it any more?
I have created a new application using the View Based Application Template in xCode.
My first view, which displays on loading the app, is a button based menu. One of the buttons (Make) should load a new navigation stack using a NavigationViewController. There are other buttons that when implemented will do other things outside of NavigationViewController's scope.
I would like to be able to click Make and open and display a new navigation controller.
From ViewController.m:
-(IBAction)makeStory:(id)sender{
NSLog(#"makeStory:");
navController = [[UINavigationController alloc] init];
makeStoryTableViewController = [[MakeStoryTableViewController alloc] initWithNibName:#"MakeStoryTableViewController" bundle:nil];
[navController pushViewController:makeStoryTableViewController animated:YES];
}
I have created a NavigationViewController in the opening ViewController.h file:
#import <UIKit/UIKit.h>
#import "MakeStoryTableViewController.h"
#interface StoryBotViewController : UIViewController {
UINavigationController *navController;
MakeStoryTableViewController *makeStoryTableViewController;
}
- (IBAction)makeStory:(id)sender;
#end
I know I'm missing something because when I call pushViewController nothing happens - I think that somehow I have to attach the NavigationViewController to the ViewController that makeStory: is in.
For reference, my app delegate header declares the view controller in the #implementation as follows:
UIWindow *window;
StoryBotViewController *viewController;
with the appropriate #synthesize in the .m of the app delegate
#synthesize window;
#synthesize viewController;
How do I push a NavigationStack on from my opening view controller?
Please forgive me if the question is a little vague, I'm happy to provide more information if you need it. It's my first time questioning on stackoverflow and I'm obviously a bit of a newbie with the iPhone SDK.
you had it almost. But you forgot to add the NavigationController to your view.
[self.view addSubview:navController.view];
Add this in your IBAction.
EDIT:
but much likely this is not what you want, because you can't navigate back to your very first viewController. If you want to navigate back to the first viewController set up your project like in the navigation-based template. You can to this programmatically to:
Move all UINavigationController stuff from the header file of the viewcontroller to the app delegate header. And change your app delegate implementation to something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
navController = [[UINavigationController alloc] initWithRootViewController:viewController];
// Add the view controller's view to the window and display.
// [window addSubview:viewController.view];
[window addSubview:navController.view];
[window makeKeyAndVisible];
return YES;
}
make sure to release navController in dealloc
then replace your IBAction with something like this:
- (IBAction)makeStory:(id)sender {
makeStoryTableViewController = [[MakeStoryTableViewController alloc] initWithNibName:#"MakeStoryTableViewController" bundle:nil];
[self.navigationController pushViewController:makeStoryTableViewController animated:YES];
}
if you don't like to display the navigationbar in your first viewcontroller hide it, but make sure to unhide it if you push other items on the stack. self.navigationController.navigationBar.hidden = ...
I need help figuring out how to change out the view in my application. I have a wonderfully working view that I have finished and now I'd like to be able to switch the view to a brand new, blank white screen to display.
I have these files:
HelloAppDelegate.h,
HelloAppDelegate.m,
HelloViewController.h, and
HelloViewController.m
Then, I added a new View Controller so now I have two more files:
SecondViewController.h and
SecondViewController.m
In my first view (HelloViewController), I have a button. When the user presses this button, I'd like SecondViewController to show up. So, in my HelloViewController.m, I have an action method
-(IBAction)switchToSecondView:(id)sender {
}
In this method, how can I go about initializing my second view and displaying it?
Thanks in advance!
If you want to do something like flipping view, making it modal and then returning back to the main view do following:
Define a delegate to indicate that secondary view finished its work
#protocol FlipsideViewControllerDelegate
- (void)flipsideViewControllerDidFinish;
#end
In main view do following:
- (void)flipsideViewControllerDidFinish {
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)showInfo {
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
}
In secondary view do following:
- (IBAction)done {
[self.delegate flipsideViewControllerDidFinish];
}