load UIViewController view over singleViewController style app - ios

I have created a single view based application. I was wanting to overlay UIViewControllers as I needed them, for instance like a modal view thing where if some values are populated then load the next view until you do something there and you can come back.
This is the code I have
- (void) viewWillAppear:(BOOL)animated {
NSMutableDictionary *tempPrefs = [prefsController readPrefs];
NSString *tempName = [tempPrefs objectForKey:#"Name"];
NSString *tempProduct = [tempPrefs objectForKey:#"Product"];
// usedbefore so skip first view (first view == login view
if ((tempName.length != 0) && (tempProduct.length != 0)) {
// you have values, enter new room without checking
[self loadGetProListViewController];
}
}
- (void) loadGetProListViewController {
[self dismissViewControllerAnimated:NO completion:nil];
getProListViewController = [[GetProListViewController alloc] initWithNibName:#"GetProListViewController" bundle:nil];
[self presentViewController:getProListViewController animated:YES completion:nil];
}
However once this method has been reached its executed but nothing is happening..
If anyone could tell me how to create modal viewControllers or some description that would be greatly appreciated.

First you should be aware of UIViewController lifecycle to have an idea where you are able to present different UIViewController
Also I guess you have a bit incorrect architecture (maybe I wrong).
Let's imagine you have firstViewController on which -viewDidAppear method you present getProListViewController
Don't you think that presenting intermediate firstViewController is odd?
I'd reather you would move your if clause to level up. F.e:
/ usedbefore so skip first view (first view == login view
if ((tempName.length != 0) && (tempProduct.length != 0)) {
// present your getProListViewController
} else {
// present your "FirstViewController" (login or whatever)
}
And you haven't faced with your issue at the beginning

Try this
[self presentModalViewController: getProListViewController animated:YES];
instead of your presentviewcontroller
[self presentViewController:getProListViewController animated:YES completion:nil];

Related

Change between two view controllers programmatically at the first launch

I have two view controllers. First is empty, second contains a text field. If this field is empty, I need to move to the second controller automatically.
I tried this:
NSUInteger VCcount = self.navigationController.viewControllers.count;
UIViewController *btVC = self.navigationController.viewControllers[VCcount-2];
if([self.btViewController.Text.text isEqualToString:#""])
{
[self.navigationController pushViewController:btVC animated:YES];
}
and this:
UIViewController* btVC = [storyboard instantiateViewControllerWithIdentifier:#"BTViewController"];
But at the first launch program knows only current controller and doesn't know about thesecond.
How can I get there?
See the example below:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.textField.text = #"some text";
if (self.textField.text.length == 0) {
UIViewController *secondVC = [self.storyboard instantiateViewControllerWithIdentifier:#"secondVC"];
[self.navigationController pushViewController:secondVC animated:NO];
}
}
If you comment out the assignment of "some text" to the textField, the secondVC will be instantiated and presented. I don't know what exactly you are trying to achieve with first two lines of your presented code.
If the secondVC already exist, you can just take a reference to it by knowing its index on the stack, or inspecting viewControllers array of your navigationController before you push it to the stack. If it does not exist, you just instantiate it from storyboard (with the correct identifier) and push it.

UICollectionView loading and UINavigationController reset of view controllers

I have a UIViewController (which is my apps root view controller) that contains a UINavigationController that I reload using
- (void)presentSelectedViewController
{
[[self navController] popToRootViewControllerAnimated:NO];
[[self navController] setViewControllers:#[[self selectedViewController]] animated:NO];
}
self.selectedViewController is set through this:
- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
if (_selectedViewController != selectedViewController)
{
if ([selectedViewController isKindOfClass:[SNPStandInViewController class]])
{
Class newClass = [(SNPStandInViewController *)selectedViewController actualClass];
if ([newClass class] == [SVWebViewController class])
selectedViewController = [[[(SNPStandInViewController *)selectedViewController actualClass] alloc] initWithAddress:[PREFERENCES urlForDefaultSearchProvider]];
else
selectedViewController = [[[(SNPStandInViewController *)selectedViewController actualClass] alloc] init];
if ([selectedViewController isKindOfClass:[SNPSettingsViewController class]])
[(SNPSettingsViewController *)selectedViewController setDelegate:[self rootController]];
}
Class oldClass = [self.selectedViewController class];
NSInteger oldControllerIndex = [self.viewControllers indexOfObject:self.selectedViewController];
[self.selectedViewController willMoveToParentViewController:nil];
[self.selectedViewController removeFromParentViewController];
[(SNPStandardViewController *) self.selectedViewController releaseResources];
[self willChangeValueForKey:#"selectedViewController"];
_selectedViewController = selectedViewController;
[self didChangeValueForKey:#"selectedViewController"];
[self.viewControllers replaceObjectAtIndex:_selectedIndex withObject:selectedViewController];
if (nil != oldClass && oldControllerIndex != NSNotFound)
{
[self.viewControllers replaceObjectAtIndex:oldControllerIndex withObject:[[SNPStandInViewController alloc] initWithClass:oldClass]];
}
[[self indexTableView] reloadData];
}
[self presentSelectedViewController];
}
(The standin view controller class is an NSObject subclass that basically holds the class of the view controller that should be there and creates it if selected -- though it is visually not a tab interface, think a tab interface where I don't want to have the actual view controllers that are not selected be created and in memory and running)
I have one view controller that has a UICollectionView in it and this is the default view controller that is set using the above code when the app first runs. It runs fine and the UICollectionView loads its data before the -(void)viewWillAppear: of that view controller runs (a -(void)loadView implementation creates the UICollectionView and does a [uiCollectionViewInstance reloadData]).
If I select another view controller above, and then reselect the initial one (which is a new instance -- old instances when moved off of are destroyed), the UICollectionView does not load its data (even though the loadView routine creates it and calls reloadData) until after both viewWillAppear: and viewDidAppear actually run. This is bad because I call a selection routine in viewWillAppear: to set a highlight in the collection view on the item that is the default selection [think a button bar type interface].
The same code path seems to run each time I create this view controller that contains the collection view (with extensive NSLog outputting to show the path). So I assume that maybe something is happening in the actual routine that creates the new view controller and sets it as the active one in the UINavigationController
I am at a loss to know what to explore to see why this collection view has no visible cells after being created and reload data being called on it when it is created after being selected in my pseudo tab controller described above (but it works fine when set initially upon creation of the pseudo tab controller).
I don't know what else people want to see but I'd be happy to try and post more info, code, etc as necessary.
in the -presentSelectedViewController I added this after doing the setViewControllers and now it all works fine.
[[[self selectedViewController] view] setNeedsLayout];
[[[self selectedViewController] view] layoutIfNeeded];

How to Pop to a View Controller without Animation

I would like to display an image in response to a local notification event that occurs while the iPhone is locked. When the user swipes on the notification, I would like my app to go to the root view controller, via popToViewController:animated, and then push a view controller that displays the image. This works when I set animated = YES. When animated = NO, the view controller that displays the image doesn't respond when the user taps the back button. Any thoughts on why the image view controller's navigation controls don't work when I popToViewController without animation? Thanks in advance.
Here's the relevant code...
- (void) localNotificationHandler
{
#ifdef kAnimatePop
animated = YES; // This works
#else
animated = NO; // This doesn't work
#endif
_showImage = YES;
// Check if this view controller is not visible
if (self.navigationController.visibleViewController != self) {
// Check if there are view controllers on the stack
if ([self.navigationController.viewControllers count] > 1) {
// Make this view controller visible
[self.navigationController popToViewController:self animated:animated];
}
}
}
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (_showImage) {
_showImage = NO;
// Show image in a view controller
[self performSegueWithIdentifier:#"MainToImageSegue" sender:self];
}
}
You don't set _showImage until after popToViewController has been invoked. If it's animated viewDidAppear won't be called until later, so it unexpectedly works. If it's not animated then viewDidAppear is called immediately, before _showImage has been set. Move _showImage = true; to before the nav controller stack manipulations.
I might do the check this way:
- (void) localNotificationHandler{
...
...
if (self.navigationController.topViewController != self) {
[self.navigationController popToRootViewControllerAnimated:NO];
}
}
I would also recommend pushing your image view controller programmatically with something like
[self.navigationController pushViewController:<imageVC> animated:YES];
If your image view controller's navigation buttons are not working, it is likely because it is sending those navigation messages to nil. aka, the imageViewControllers self.navigationController is equal to nil.
I'm not sure how to fix this with storyboards, but if you present it programatically that problem should go away.

How do I manually set the "Back" destination in iOS apps

I have a UIViewController called 'detailViewController'.
This view controller is accessed through multiple different segues using the push segue.
The problem I am facing is that the back button on the detailViewController takes the user back to the previous view controller (as it should), however I would like the back button to take the user to the masterViewController (the default UIViewController in the app).
How do I do this?
I have tried changing the segue types however that didn't really do anything at all.
Peter
The method you're looking for is UINavigationController's popToRootViewControllerAnimated:
From the documentation: "Pops all the view controllers on the stack except the root view controller and updates the display."
You'll need to create a custom back button. You can't afaik override the back button's functionality.
So something like:
UIButton *myBackButton = [UIButton buttonWithType:UIButtonTypeCustom];
[myBackButton addTarget:self action:#selector(popToRoot:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *myCustomBackButtonItem = [[UIBarButtonItem alloc] initWithCustomView:myBackButton];
[self.navigationItem setLeftBarButtonItem:myCustomBackButtonItem];
and then the implementation of popToRoot: would look like:
- (void)popToRoot:(id)sender
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
If the masterViewController you're talking about is the root you can call:
[self.navigationController popToRootViewControllerAnimated:YES];
You can create the back button yourself and call [self.navigationController popToRootViewControllerAnimated:YES]. However, that does require you to create the back button yourself. An alternative option could be to take the current view controller, and remove all of them except the first and current ones:
NSMutableArray *viewControllers = [self.navigationController.viewControllers mutableCopy];
[viewControllers removeObjectsInRange:NSRangeMake(1, [viewControllers count] - 2)];
self.navigationController.viewControllers = viewControllers;
Use the following code in order to go back to main view controller:
[self.navigationController popViewControllerAnimated:YES];
Edit:
Based on the comments, I realize that my code takes you only one step back and may not answer what is asked in this question. Thanks.
I came up with a workaround that looks up in a navigation controller's collection and find the offset between the existing and destination controller using name of destination controller and remove the in-between controller. Not sure if it is close to your need but give it a try:
- (NSArray *)navigateToViewController:(NSString *)destinationControllerName withControllers:(NSArray *)sourceControllers
{
NSMutableArray *controllers = [[NSMutableArray alloc] initWithArray:sourceControllers];
NSInteger sourceControllerIndex = [controllers count] - 1; // Value should be index oriented instead of position oriented.
NSInteger destControllerIndex = 0;
// Find position of destination controller (index oriented)
for (UIViewController *controller in controllers)
{
NSString *controllerName = NSStringFromClass([controller class]);
NSLog(#"class name: %#", controllerName);
if([[controllerName lowercaseString] isEqualToString:[destinationControllerName lowercaseString]])
break;
destControllerIndex +=1;
}
// If desired controller does not exists in the stack, do not proceed.
if (destControllerIndex == sourceControllerIndex)
{
return controllers;
}
// If destination is the same as source do not enter in this block
// If destination and source controllers have zero or no controllers in between do not enter in this block
// If destination and source controllers has few controllers (at least one) in between, go inside this block.
// Here "destControllerIndex + 1" shows, there is at least one controller in between source and destination.
else if(destControllerIndex + 1 < sourceControllerIndex)
{
NSLog(#"controllers in stack: %#", controllers);
// Start from the controller following the source controller in stack and remove it
// till the destination controller arrives
for (NSInteger iterator = sourceControllerIndex - 1; iterator > destControllerIndex; iterator--)
{
UIViewController *cont = [controllers objectAtIndex:iterator];
if(cont) {
[cont release]; cont = nil; }
[controllers removeObjectAtIndex:iterator];
NSLog(#"controllers in stack: %#", controllers);
}
}
return controllers;
}
and define it in some base controller:
-(void) popToViewController:(NSString *)controllerName withNavController:(UINavigationController *)navController
{
// STStatistics refers a singleton class of common functions.
NSArray *mArr = [STStatistics navigateToViewController:controllerName withControllers:navController.viewControllers];
navController.viewControllers = mArr;
// There should be more than one controller in stack to pop.
if([mArr count] > 1)
{
[navController popViewControllerAnimated:YES];
}
}
and here is how called:
[self popToViewController:#"NameOfDestinationControllerInNavStack" withControllers:self.navigationController];

Changing content of UINavigationController's Stack result in crash

I have a UINavigationController with 4 items:
(root)mainvc -> callerlistvc -> addcallerformvc -> verifycallervc (in that specific order)
When I am on the verifycallervc screen, if I press back, I want to go back to callerlistvc.
Here is the catch however, the back button should be a system button.. So.. as far as I know I cannot replace the action with a selector calling poptoviewcontroller:animated (only works on a custom uibarbuttonitem)
So then I thought of manipulating the stack (pretty interesting and challenging too!) So here is what I did...
So currently Im on the verifycallervc screen... and this gets called.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSMutableArray *allViewControllers = [self.navigationController.viewControllers mutableCopy];
__block UIViewController *mainvc = nil;
__block UIViewController *callerlistvc = nil;
__block UIViewController *addcallerformvc = nil;
[allViewControllers enumerateObjectsUsingBlock:^(UIViewController *vc, NSUInteger idx, BOOL *stop) {
if ([vc isKindOfClass:[MainVC class]]) {
mainvc = vc;
} else if ([vc isKindOfClass:[CallerListVC class]]) {
callerlistvc = vc;
} else if ([vc isKindOfClass:[AddCallerFormVC class]]) {
addcallerformvc = vc;
}
}];
[self.navigationController setViewControllers:#[ mainvc, callerlistvc, self]];
}
After I did that, I pressed back normally and was now on the callerlistvc... great.
Unfortunately when I press the button (push-segued to addcallerformvc)... it results in a crash EXC_BAD_ACCESS.
I also tried a different approach by first manipulating the variable callerlistvc like so before adding it in the setViewControllers method
callerlistvc = [[UIStoryboard storyboardWithName:#"main" bundle:nil] instantiateViewControllerWithIdentifier:#"CallerListVC"];
But the result is the same.
I have added breakpoints and it goes like this...
CallerListVC:
tappedShowAddCallerListButton
performSegueWithIdentifier
prepareForSegue // identifier string is correct, destinationVC is not nil
then AddCallerFormVC:
4. viewDidLoad
5. viewWillAppear // properties not nil
after that EXC_BAD_ACCESS occurs
How can I make this work?
The better approach in this case would be to use a custom UINavigationController class and extend the popViewControllerAnimated: method. In this method either call the super method or pop to the specific view controller (super class method) based on a check. That way you have your system nav buttons and also control where the tack should pop to.
Don't override anything in verifycallervc and instead do following with normal back on verifycallervc
Override viewWillAppear or viewDidAppear for addcallerformvc like this
- (void)viewDidAppear:(BOOL)animated
{
if (![self isBeingPresented]) {
[self.navigationController popViewControllerAnimated:YES];
}
}
Reference :
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDisplay-Notifications/RespondingtoDisplay-Notifications.html#//apple_ref/doc/uid/TP40007457-CH12-SW7
Note: not tested, don't have XCode right now....

Resources