Change Storyboard Nav View Controller on app open without noticeable transition. - objective-c

I have a basic ui navigation view with a table view, when a user selects a name and presses the top bar next button it should go to the main ui navigation view, after this initial setup it should start on the main view controller what's the best way to go about this?....
I currently have this:
My end goal is on complete of the LoginNavController it would "push" to the new NavController and on open always go straight too the new NavController. How do I do this in a efficient (and proper?) way?

I'm not sure what you mean with
My end goal is on complete of the LoginNavController it would "push" to the new NavController and on open always go straight too the new NavController. How do I do this in a efficient (and proper?) way?
If what you mean is that you would like to show the login view only once, and then always show the other view controller, one possible solution would be to use a modal segue to the next navigation controller (identified by the ID modalSegue in my code snippet). Once the login view controller has achieved its goal, you could save it through the use of NSUserDefaults (these are persistent between app launches). In the viewWillAppear method of the login view controller you could then check the value for the relative key, and if it is set then perform directly the segue to the other navigation view controller. It would be something like:
-(void)viewWillAppear:(BOOL)animated
{
NSNumber* result = [[NSUserDefaults standardUserDefaults] valueForKey:#"isSet"];
if(result.boolValue)
[self performSegueWithIdentifier:#"modalSegue" sender:self];
}
And to set the key once the loginViewController is done you would do something like:
-(void) setLoginCompleted
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"isSet"];
}
EDIT: For a smoother user experience, you could directly set the rootViewController of your application window in the didFinishLaunchingWithOptions: delegate's method. In this case you would do something like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MyNavController *nav = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"myNavVC"];
NSNumber* result = [[NSUserDefaults standardUserDefaults] valueForKey:#"isSet"];
if(result.boolValue)
[self.window setRootViewController:nav];
return YES;
}

Related

UIPageViewController within NavigationController

I've read every tutorial I've found about UIPageViewController, but they show just basics, I'd like to create something like new twitter app has:
UIPageViewController is embedded into Navigation controller, title of navigation bar is based on current page and those page dots are there as well, user can tap on item on current page(item from table view/collection view) to see detail.
I was able to come up with something similar, each page had collection view, and showing detail of some item was reflected in navigation bar, there was correct title and "<" button, but I wasn't able to change title based on currently shown page
Please, could you describe me how to do this in few steps/basic structure of controllers?
Don't know if you are still working on this but here goes anyway. To set up a UIPageViewController you might use the tutorial and two questions below.
http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/
How to implement UIPageViewController that utilizes multiple ViewControllers
How to add UIBarButtonItem to NavigationBar while using UIPageViewController
The last link pertains specifically to setting the contents of the navigationBar depending on what you are viewing.
The key is to create a UINavigationItem Property in the .h file of your UIPageViewController content view controllers, meaning the ones/one that are displaying whatever it is you are displaying.
From my code in FirstViewController.h SecondViewController.h and ThirdViewController.h
#property (strong, nonatomic) UINavigationItem *navItem;
In the second and third links above you'll see a storyboard layout of a Master-Detail application (which uses a navigation controller). The UIPageViewControllerDataSource is the DetailViewController. The three pages associated with the pageViewController are my content view controllers.
In DetailViewController.m you have to instantiate the contentViewControllers somewhere. At that point you pass the DetailViewControllers navigationItem id to the content view controllers. Here is how I instantiate my content view controllers using the delegate methods of the UIPageViewController.
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSString * ident = viewController.restorationIdentifier;
NSUInteger index = [_vc indexOfObject:ident];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
if (index == 0) {
return [self controllerAtIndex:index];
}else if (index == 1){
return [self secondControllerAtIndex:index];
}else if (index == 2){
return [self thirdControllerAtIndex:index];
}else{
return nil;
}
}
The delegate method calls the method below. It is almost directly from the tutorial link with just a few modifications.
-(FirstController *)controllerAtIndex:(NSUInteger)index
{
FirstController *fvc = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstPageController"];
fvc.imageFile = self.pageImages[index];
fvc.titleText = self.pageTitles[index];
fvc.pageIndex = index;
fvc.navItem = self.navigationItem;
return fvc;
}
Notice that properties are passed into the view controller including self.navigationItem. Passing it in ensures you can make changes to the navigationBar items.
Then in the viewDidAppear method of your content view controller you can simply set the title on the navigation bar like this.
navItem.navigationItem.title = #"Whatever you want the title to be";
It is important to use viewDidAppear because viewDidLoad is not called every time the screen appears. I believe the UIPageViewController caches the page before and the page after whatever you are viewing which saves the system from having to load the page every time you navigate to it.
If you are using a single view controller for all you pages like the tutorial does you will have to use the index property to know what to set the title to.
Hope this helps!
I had a very similar setup and solved the problem.
My setup is that I have a UIPageViewController inside a UINavigationController because I wanted the navigation bar to be persistent while I swiped between each view controller. I wanted the title of the current UIViewController inside the UIPageViewController to become the title of the UINavigationController.
The way to do this is to implement the UIPageViewControllerDelegate method pageViewController didFinishAnimating which triggers after a change to a view controller is made from the UIPageViewController. You can probably see where this going: From here, set the navigationItem.title property of the UIViewPageController, which the UINavigationController uses to set it's own title, with that of the current view controller's title.
Example:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if(finished && completed)
{
NSString *titleOfIncomingViewController = [[pageViewController.viewControllers firstObject] title];
pageViewController.navigationItem.title = titleOfIncomingViewController;
}
}
NB: This delegate method triggers only off gesture-initiated scrolls.
Solution given by Mr. Micnguyen is the exact solution but the mentioned delegate method didFinishAnimating() is called when swipe action is done due to which initially title is not shown.
Hence to resolve that problem, we need to set its initial value in viewDidLoad() method of UIPageViewController class as mentioned below:
- (void)viewDidLoad(){
self.navigationitem.title = arrayname[0];
}
I had this question too, but ended up doing something different, because I'm using Swift, so I thought I'd share here.
I ended up embedding a UIPageViewController in a Container View in my Navigation Controller. On a page swipe, I used pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:), with my PageViewController as the UIPageViewDelegate. From there I created a protocol that I used to send data about the VC displayed to the parent VC, and change the title using self.title = "My Title".
I didn't make the parent VC the UIPageViewDelegate because I found it to be easier to access the displayed VC from the PageViewController than from the parent VC as let childVC = pageViewController.viewControllers![0] as! DetailViewController.

How do I properly use NSUserDefaults Class to bypass a View?

I have a project in xcode that uses storyboards. The first view that loads is an "accept terms and conditions" view in which the user must click an accept button to proceed. After clicking it, it segues to the next view. After the user clicks accept the first time the program launches, I never want them to see that view again - I want it to go straight to the next view. I have some code but its not working. Here is what I have exactly:
In app delegate: (inside applicationDidFinishLaunchingWithOptions)
if([[NSUserDefaults standardUserDefaults] boolForKey:#"TermsAccepted"]!=YES)
{
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"TermsAccepted"];
}
Inside the accept terms and conditions view implementation: (viewDidLoad)
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"TermsAccepted"]){
[self.navigationController pushViewController: self animated:YES];
//I want it to go to the next screen
}
else {
//I want to show this screen, but I don't know what goes here
}
Also Inside the accept terms and conditions view implementation (in the accept button)
- (IBAction)acceptButton:(id)sender {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"TermsAccepted"];
}
I run it and get the error: 'Pushing the same view controller instance more than once is not supported'. How do I fix this?
In your first code snippet, you basically say "if TermsAccepted is not YES (so it is NO), then set it to NO. This does not make sense
In your 2nd code snippet, you wrote [self.navigationController pushViewController:self animated:YES];. So basically you ask the current UIViewController (self) to push itself on its own navigationController… which does not make sense either.
That's why you have this error. You try to push the current viewController self whereas it is already on screen in your navigationController. So you try to push the same instance (self) twice on the same navigationController.
You obviously meant to push another viewController (probably an instance of a TermsAndConditionViewController or something that shows the terms and conditions of your app) on the navigation controller, and not the current viewController itself, which doesn't make sense.
First, you want to have the next view controller, the one you always want to show, be the root view controller of your window. In that controller's viewDidLoad method, put your if clause to show the accept terms and conditions controller -- you can show that one using presentModalViewController. The if clase can be like this:
If([[NSUserDefaults standardUserDefaults] BoolForKey:#"TermsAccepted"] !=YES) {
// instantiate your terms and conditions controller here
// present the controller
}
Then, in the method where you dismiss the terms and conditions controller, set the value of that key to YES.

Get current view

I have an app which has split view inside a tab bar, and these split views often have navigation hierarchy and then sometimes modal views are presents on top of them, and it all works fine, but...
I am trying to display a passcode lock whenever the app goes into background, so I put
[self.window.rootViewController presentModalViewController:lockView animated:YES];
in my AppDelegate's method
- (void)applicationWillResignActive:(UIApplication *)application
...which works fine unless a modal view is displayed.
the passcode does not display if a modal view is open.
Is there a way to retrieve the currently active view controller so I can present this lock view?
Thanks in advance
Cheerio
Code that worked was as follows:
BOOL hasKids = YES;
UIViewController *topViewController = (UIViewController*)[[(UITabBarController*)self.window.rootViewController viewControllers] objectAtIndex:((UITabBarController*)self.window.rootViewController).selectedIndex];
while (hasKids) {
if (topViewController.presentedViewController) {
hasKids = YES;
topViewController = topViewController.presentedViewController;
} else {
hasKids = NO;
}
}
[topViewController presentModalViewController:lockView animated:YES];`
I think the easiest way is to keep track of which tab is currently active (there are a number of ways to do this, but I'd recommend implementing the UITabBarControllerDelegate and handling its tabBarController:didSelectViewController: method).
Once that's done, you'll probably need to manage a property in each view controller that holds any modal view controllers you present. If, however, you're on iOS 5 or higher, look into the UIViewController property presentedViewController. It appears that this is a new way to do exactly what you want.

Push segue without animation

I'm writing a storyboard-based iPhone app and working on state restoration. When performing the segues normally, I want to have them animate, but when I'm restoring several levels of a navigation hierarchy, I only want the last segue to animate. Other than setting up two sets of segues—one set that uses a normal push segue, and another that uses a custom non-animating push segue—is there any way to achieve what I'm trying to do?
It's possible to directly manipulate the view controller stack, independently of the application's segues or storyboards.
You can use this technique to restore a deep stack of view controllers, and perform / animate just a single segue to the top view controller. (You will likely need to create a specific push segue for this purpose.)
For example, to restore a two view controller stack, you could do the following. In this example, it's assumed that some action on an existing view controller leads to the state restore, but you can just as easily perform it from your App Delegate.
[self performSegueWithIdentifier:#"Page2Express" sender:self];
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Page2Express"])
{
// Get any state data you need to from Core Data
CoreDataType *valuePulledFromCoreData = // ...
// Set up the page 2 view controller as you normally would
Page2ViewController *page2ViewController = segue.destinationViewController;
page2ViewController.instanceVariable = valuePulledFromCoreData;
// Create a loose, page 1 view controller and set it up as required
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
Page1ViewController *page1ViewController = [storyboard instantiateViewControllerWithIdentifier:#"Page1ViewController"]; // Ensure you have this identifier set up in your storyboard
page1ViewController.instanceVariable = valuePulledFromCoreData;
// Add the page 1 view controller to the top of the navigation stack (to be later obscured in the segue by the page 2 view controller)
NSMutableArray *viewControllers = [[self navigationController].viewControllers mutableCopy];
[viewControllers addObject:page1ViewController];
[self navigationController].viewControllers = viewControllers;
}
}
If, instead, you prefer to have no animations, then it's easier still. You can restore state solely by manipulating the view controller stack (and without using any segues) from - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions and - (void)applicationWillEnterForeground:(UIApplication *)application.
Either way will work seamlessly and in tandem with your existing storyboard(s) and segues.

TabBarController NAvigationController and UIViewController

I have the next question:
In my project I have the next:
UItabbarController
....Some UINAvigationControllers....
*(1) UINavigationController
UIViewController (UItableView) - When select one row it goes to...(by push) to:
UIViewController (UItableView) - And here the same than before, for each row i open a new tableview....
My problem is when i click in the tab bar item, I see the viewController view like last time that i saw this, and no reload to the *(1) first view another time( like i would like)
Where I need to write sth for each time that i click in a tab bar item i reload the first view of this tab bar item.
PD: I have the call: [theTableView reloadData]; in method "viewWillAppear".
The thing I'm doing is:
In my navigation Controller I have a View Controller (like tableview) and when i click in one row, in the "didSelectRowAtIndexPath" method I create another View Controller calling "myController" and i push this element like this ( [[self navigationController] pushViewController:myController animated:YES];)
And this for each time i click in one row in the next tables.
Then I think the problem is not to reload the table view in the method viewWillAppear, it's to take out from the screen the next views controller that I inserted to the root one.
I'm rigth?
IN RESUME:
My app has the next:
Tab bar to move between screens
Navigation inside each tab bar (as far as you want), why? because all the tabBarItems show Tables, and if you click in one row you open another table,.....
My problem then is that I would like to come back to the 1st Main table when i click in the tab bar. For the moment the app doesn't do this, it continue in the scree(table view) that was the last visit in this tab. (Is not completely true, because if i click two time, Yes, it comes back but don't enter in the "viewWillLoad" or "didSelectViewController" methods, because i made NSLogs and it doesn't show them).
The sketche can be this:
AppDelegate -> WelcomeScreen ->VideosTableViewController ->RElatedVideosTableViewController -> ..... ..... ....
The 1st thing is to show the Welcome screen (not so important, only some buttons) and in this class i have the TabBArController initialized with "localViewControllersArray" that is a NSMutableArray of NavigationControllers each initialized with one ViewController .
Then when i press one of the buttons in this welcome Screen i sho the tab bar Controller (Shows the VideosTableViewController)
In the next step, when I click in one row, in "DidSelectRowAtIndexPath" I create a RElatedVideosTableViewController, and I push this by "[[self navigationController] push....: "The relatedvideo table view i create" animated:YES];
AND I ALSO HAVE:
Add: UITabBarControllerDelegate
Add:
(void)tabBarController:(UITabBarController*)tabBarController
didEndCustomizingViewControllers:
(NSArray*)viewControllers
changed:(BOOL)changed { }
(void)tabBarController:(UITabBarController*)tabBarController
didSelectViewController:(UIViewController*)viewController
{ if ([viewController
isKindOfClass:[UINavigationController
class]])
{ [(UINavigationController *)viewController popToRootViewController:NO];
[theTableView reloadData];
NSLog(#"RELOAD");
} }
And at the initialization of the class:
[super.tabBarController setDelegate:self];
But in the console I don't see the NSLog I'm making then is not going in this method.
Make your app delegate the tab bar controller's delegate, either in Interface Builder or in code:
- (void)applicationDidFinishLaunching
{
...
self.tabBarController.delegate = self;
}
Then, when the tab bar switches to a different view, you get notified, at which point you pop to the root of the selected nav controller thus:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
if ([viewController isKindOfClass:[UINavigationController class]])
{
[(UINavigationController *)viewController popToRootViewController:NO];
}
}
Each view controller should have its own table view, so I don't know what you are trying to do by the reload.