ios change storyboard default view controller at run time - ios

It it possible to override the default view controller from a storyboard to show a different controller instead? This would all happen in the AppDelegate of course.

#Martol1ni I wanted to use your answer, but I also wanted to stay away from unnecessary storyboard clutter so I tweaked your code a bit. However I did give you a +1 for your inspiring answer.
I put all of the following on the default controller.
- (void)gotoScreen:(NSString *)theScreen
{
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:theScreen];
[app.window setRootViewController:screen];
}
And then where the logic happens I'll call the following as needed.
if(myBool == YES) {
[self gotoScreen:#"theIdentifier"];
}

I would definately embed a rootView in a UINavigationController, so you have not two, but three views. The one is never launched, just in control of all the otherones. Then implement the methods in it like this:
- (void) decideViewController {
NSString * result;
if (myBool) {
result = #"yourIdentifier";
}
else {
result = #"yourOtherIdentifier";
}
self.navigationController.navigationBarHidden = YES; // Assuming you don't want a navigationbar
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"view1ident"];
[self.navigationController pushViewController:screen animated:NO]; // so it looks like it's the first view to get loaded
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self decideViewController];
}
It never looks like the first view is loaded. If you are using NIBS, you can do everything from AppDelegate though...

Related

Objective c - Detect current view from appDelegate

I'm using modal segue (without navigation controller) to move between viewController A and viewController B like so:
viewA *first = [self.storyboard instantiateViewControllerWithIdentifier:#"viewA"];
[self presentViewController:first animated:YES completion:nil];
And to move back :
[self dismissViewControllerAnimated:YES completion:nil];
Now, I want to know from the AppDelegate whether A or B is the current view right now.
The problem is when I'm checking
[(AppDelegate *)[[UIApplication sharedApplication] delegate] window]
the answer is always view A - the first one.
I've tried to set the current view every time I'm using modal segue like so:
viewA *first = [self.storyboard instantiateViewControllerWithIdentifier:#"viewA"];
[self presentViewController:first animated:YES completion:^
{
[[(AppDelegate *)[[UIApplication sharedApplication] delegate] window] setRootViewController:first];
}];
But it cause a few bugs (like unable to use "dismissViewControllerAnimated"),and it's impossible to work like that in every segue in a big project with many segues.
How should I work with that? And how should I detect the current view in more appropriate way?
As was answered here
UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
return win1.windowLevel - win2.windowLevel;
}] lastObject];
UIView *topView = [[topWindow subviews] lastObject];
However, doing this logic here sounds like bad architecture. What is the reason for you needing to know which view is currently presented inside of your AppDelegate?
Edit It seems like you want to respond to the applicationWillResignActive event from your view controller. Use something like this in the your game view controller.
- (void) applicationWillResign {
NSLog(#"About to lose focus");
}
-(void) viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(applicationWillResign)
name:UIApplicationWillResignActiveNotification
object:NULL];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter]
removeObserver:self];
}
The appDelegate's window won't be equal to either view controller (viewControllerA or viewControllerB). You can ask the window for it's root view controller...
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (appDelegate.window.rootViewController == viewControllerA) {
// always true if you always start the app with viewControllerA
... and you can ask any view controller for the view controller it presented...
if (appDelegate.window.rootViewController.presentedViewController == viewControllerB) {
// will be true if viewControllerA has presented viewControllerB
But this is a tricky game. If, for example, viewControllerB presents some other viewControllerC, the condition above will continue to be true.
See the #Eric answer here (not the accepted answer) for a way to find the topmost vc in general.

UIScrollView disabled after UINavigationController push and pop

I have looked at the other answers to this question, and none of them have helped.
I have a UIScrollView in my very simple scene, embedded like this:
I use this code to make sure the scroll view will actually scroll, based off of this answer.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self resizeScrollView];
}
- (void) viewDidLayoutSubviews {
[self resizeScrollView];
}
- (void)resizeScrollView
{
_scrollView.scrollEnabled = YES;
[_scrollView setContentSize:_innerView.frame.size];
}
- (IBAction)cameraButtonPressed:(id)sender {
UIViewController *vc = [UIViewController new];
[self.navigationController pushViewController:vc animated:YES];
}
The problem is that scrolling doesn't work after I've pushed and popped the new ViewController (Which originally was an image picker btw).
I don't know why this worked, but I wrapped everything in another view, and it's perfect now.

UISplitView with Multiple Detail Views (with Storyboard)

I've been trying to create a version of this code using a storyboard:
I want to be able to switch between two different detail views, depending on the cell selected in the navigation table. I've tried to implement this by creating a SplitViewManager with a custom setter method that swaps out the detail views each time a different cell is selected. This is the same approach that Apple's sample code uses. The SplitViewManager follows the delegate.
I think my issue is that I haven't connected my splitViewController.delegate to anything, so I can't assign the splitViewManager to anything either. But I can't figure out what I would even connect the delegate to in the storyboard. Please let me know if I'm being an idiot here (almost definitely). Thanks!
My code is below:
DFMAppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.splitViewController = (UISplitViewController *)self.window.rootViewController;
self.splitViewManager = (DFMSplitViewManager *)self.splitViewController.delegate;
NSLog(#"split view controller: %#", self.splitViewController); // not null
NSLog(#"split view controller delegate: %#", self.splitViewController.delegate); // is null
NSLog(#"split view manager: %#", self.splitViewManager); // is null.
// But i'm not sure how to assign splitViewController.delegate or splitViewManager in the storyboard.
return YES;
}
DFMSplitViewManager.m:
- (void)setDetailViewController:(UIViewController<SubstitutableDetailViewController> *)detailViewController
{
self.detailViewController = detailViewController;
// Update the split view controller's view controllers array.
// This causes the new detail view controller to be displayed.
UIViewController *navigationViewController = [self.splitViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [[NSArray alloc] initWithObjects:navigationViewController, self.detailViewController, nil];
self.splitViewController.viewControllers = viewControllers;
}
DFMMasterViewController.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DFMAppDelegate *appDelegate = (DFMAppDelegate *)[[UIApplication sharedApplication] delegate];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
if (indexPath.row == 0) {
NSLog(#"clicked cell 1");
DFMDetailViewController *detailViewController = [storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
[appDelegate.splitViewManager setDetailViewController:detailViewController];
}
else {
NSLog(#"clicked cell 2");
DFMDetailCollectionViewController *detailCollectionViewController = [storyboard instantiateViewControllerWithIdentifier:#"CollectionViewController"];
[appDelegate.splitViewManager setDetailViewController:detailCollectionViewController];
}
}
Turns out you can use the interface builder to add NSObjects to View Controllers. Once I did that, I changed the NSObject's class to DFMSplitViewManager, set it as the SplitViewController's delegate, and it was pretty straight forward from there.
I am facing exactly the same problem as yours. Don't know wether you find out the solution or not, here is the solution I found.
Use following code in your AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
self.detailViewManager = [[DetailViewManager alloc] init];
self.detailViewManager.splitViewController = splitViewController;
self.detailViewManager.detailViewController = splitViewController.viewControllers.lastObject;
splitViewController.delegate = self.detailViewManager;
if ([splitViewController respondsToSelector:#selector(setPresentsWithGesture:)])
[splitViewController setPresentsWithGesture:YES];
return YES;
}
The rest of code is the same as what Apple has provided.
Basically, self.detailViewManager is our split view controller, when you select cell in table, self.detailViewManager will reset the detail view (if I'm not wrong). I'm new to Xcode, so anyone please correct me if I'm wrong.
Here is the solution link, answered by hallmark.

ios presentModalViewController with two views

I'm confused. I have a navigation controller with a BarItem which opens a first view. After some work is done, I want this view to disappear and I want a second view to open.
root view: navigation controller
first view: activity indicator, where some data is put together
second view: MFMailComposeViewController
In the root view, the BarItem runs these lines to open the first view:
IndicatorViewController *indicator = [[IndicatorViewController alloc] initWithNibName:#"IndicatorViewController" bundle:nil];
indicator.view.backgroundColor = [UIColor clearColor];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:indicator animated:YES];
The first view (IndicatorViewController) does some work and finally runs
[self dismissModalViewControllerAnimated:YES];
This works fine. But - how do I open the second view?
I tried this:
I open the second view. After closing the second view, my first view pops up again (since it is still there) and get's dismissed at this point. This code is placed in the first view:
- (void) viewDidAppear:(BOOL)animated {
static BOOL firstTime = YES;
if (firstTime) {
//do stuff that takes some time (that's why I show the indicator)
MailViewController *controller = [[MailViewController alloc] init];
if (controller)
[self presentModalViewController:controller animated:YES];
firstTime = NO;
} else {
[self dismissModalViewControllerAnimated:YES];
}
}
Since the first view pops up again, the user can see the indicator one more time, after the second view is closed - and that is not what I want.
What am I missing here? What would be the better way to do this?
I would do something like this. Make a navigationController, and make the first view as the root controller. Then do something like this:
FirstView.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
}
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
Then in the second view:
SecondView.m
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
And finally in the rootview:
RootView.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *navStack = [NSArray arrayWithObject:self];
self.navigationController.viewControllers = navStack;
[self.navigationController setNavigationBarHidden:NO];
}
This will make your RootView the new rootview of the NavigationController.
self.navigationController.viewControllers
is the array with all the ViewControllers that are on the navcontrollers stack. The first object is the rootcontroller. If we replace the whole array with our own array, it knows only one item. You CAN go back by dismissing if that's what you want though. This isn't the prettiest way of doing it, but it's not the worst either.

Present storyboard view controller from app delegate?

I have a view controller subclass, SignInViewController, used for sign in that might be needed at any time. Rather than have every view controller in my app listen for the notification that sign in is needed, I'd rather have the app delegate do it.
But how do I trigger it from my app delegate?
Do I put the SignInViewController in my main storyboard? If so, how do I access my storyboard from my app delegate? Or is some other approach better?
You can always reference to your app delegate through the UIApplication singleton.
From there you can always get your root view controller.
With your root view controller you can get a reference to the storyboard.
Once you have your story board all you do is instantiate an instance of the view controller you want.
Present it.
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
MainViewController *mvc = (MainViewController *)appDelegate.window.rootViewController;
LoginViewController *lvc = [mvc.storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
[currentVC presentModalViewController:lvc animated:YES];
There may be a more direct way of getting a reference to your storyboard but this will almost always get it for you.
To show a view controller from anywhere (including the app delegate), I have used this code with success in iOS 8+ (I'm not sure about earlier compatibility). It will present from the modal view if there is one.
YOURAppDelegate *appDelegate = (YOURAppDelegate *)[[UIApplication sharedApplication] delegate];
UINavigationController *rootNavC = (UINavigationController *)appDelegate.window.rootViewController;
UIViewController *topVC = rootNavC.topViewController;
UIViewController *myNewVC = [rootNavC.storyboard instantiateViewControllerWithIdentifier:<YOUR STORYBOARD ID>];
if (topVC.presentedViewController)
{
if ([topVC.presentedViewController class] == [UINavigationController class])
{
dispatch_async(dispatch_get_main_queue(), ^{
[((UINavigationController*)topVC.presentedViewController) pushViewController:myNewVC
animated:YES];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[topVC.presentedViewController.navigationController pushViewController:myNewVC
animated:animated];
});
}
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[rootNavC pushViewController:myNewVC
animated:animated];
});
}
I usually wrap this in a convenience method and pass in my instantiated view controller. To OP - you would create SignInViewController in the storyboard, assign it a unique storyboard ID, and then substitute that ID in the above code.

Resources