Programming UIViewControllers cycles with ios and storyboard - ios

I am just starting ios development with IOS 5 and storyboards and would like to know how I can solve the following problem:
I have a tree hierarchy with categories an products being nodes of the tree and products are leaves. I created a TileViewController view to display all nodes at a specific level. When the user selects a node in the TileView, depending on its type (leaf or not-leaf) I either push a new TileViewController into the application NavigationController or I show a DetailViewController.
What I do currently is to do everything programmatically(By that I mean tracking what the user clicked on in the TileViewController and decide what to push next in the NavigationController). I get a handle on the storyboard and use the below code to create the next TileViewController if needed.
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
TileViewController *controller = [storyBoard instantiateViewControllerWithIdentifier:#"TileViewController"];
controller.model = self.model;
[self.navigationController pushViewController:controller animated:YES];
What I can't figure out is how to make this using a storyboard instead of hardcoding logic in the TileViewController. Is this at all possible? I thought about using a single TileViewController and just reloading the tiles displayed based on the user selection but then I lose the navigation piece. Any ideas how I could achieve this?
Thank you.

You can define a segue from whatever UI component kicks off the navigation, back to the same scene in the storyboard - so if it was from a button, ctrl-drag from the button down to the yellow sphere representing the tile view controller.
Define this as a push segue, then pass the relevant detail in prepareForSegue. This will create a potentially infinite navigation stack without having to repeat yourself in the storyboard.

Related

Storyboard segues and receiving memory warning

I am developing an application with iOS 9 based SDK , this is my first time I am working with Storyboards , I have 20 view controllers, each scene has Next / Previous buttons to go back and forward . I have a huge problem with going forward !. If I move from scene 1 to for example to scene 15 I received memory warning and then application crashes . I have searched and it seems there is method called unwind segue but it seems this is for going back ! it's something like dissMiss method .
I connect each scene with line in Interface Builder :
Here is segue's setting :
I would be grateful if you help me out .
EDITED :
I tried to present a view controller programmatically but result was the same ! .
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
WhatIsDino *vc = (WhatIsDino*)[mainStoryboard instantiateViewControllerWithIdentifier:#"WID"];
[self presentViewController:vc animated:YES completion:nil];
Seems like it's a problem of wrong approach, and not the storyboard.
Let me guess, since before storyboard you used to change your app's rootViewController to the next/previous screen once you tap on the arrow button. So previous screen are released and deallocated from memory once you set a new rootViewController.
And now you're presenting every next view controller modally, which involved creating new UIWindow and loads all the hierarchy of you screen and keeps previous underneath the new one so it holds the memory and you're getting out of memory crash.
Well, you can do rootViewController approach with a storyboard too since it's just another way to manage your screens while development. Storyboard offers additional features like segues, static table view cells, general tint color and so on. [UIStoryboard -instantiateViewControllerWithIdentifier:] is the method you might find interesting.
But I'd rather recommend you to check out the UIPageViewController, it's like a container for the screens. Unfortunately, it cannot have the segues to your scenes (because of the special way segues work) so you have to use -instantiateViewControllerWithIdentifier: method anyway. You can treat inner view controllers of UIPageViewController as you do with rootViewController before.
You can also navigate without segue and Its easy way I think.
If you want to navigate from Class1 to Class 2 then follow these steps.
1) In Class 1, Import Class2.
2) In your button Action, Write this code.
Class2 *next = [self.storyboard instantiateViewControllerWithIdentifier:#"Class2 Identifier name"];
[self.navigationController pushViewController:next animated:YES];
Do not forget to give Identifier name in story board that is "Storyboard ID" in Attribute inspector of particular class.
No need to add Segue,Your storyboard would look clean.
The problem is that you are adding view controller after view controller with modal presentation. That causes each view controller to be added on top of the previous one, and all of them accumulate, using more and more memory.
Using a navigation controller and a push also piles the view controllers on top of each other.
You will have this problem if you use storyboards, nibs, or create the view controllers manually.
If you have a design where the user can move through a large series of view controllers then you probably want to dismiss the previous one before pushing/presenting a new one.
You can probably dismiss the previous view controller without animation and then present the new view controller each time you want to display a new one and avoid the memory issue. i'd have to experiment with it to get the effect I was after, but that's what I would suggest.

Stop Navigation Controller from affecting other View Controllers

I have an app that uses a Navigation Controller with three View Controllers for step-by-step user setup. So the first View would be Step 1, second Step 2, and etc. And all this would be embedded in a Navigation Controller so the user would be able to go back and forth. However, once this setup is done and the user presses the 'Done' button, the app programmatically moves to another section of the storyboard which is supposed to be separate from the initial setup. I programmatically segue to this section with the following code:
let vc : AnyObject! = self.storyboard!.instantiateViewControllerWithIdentifier("welcomeViewController")
self.showViewController(vc as! UIViewController, sender: vc)
where "welcomeViewController" is the identifier for the post-setup ViewController. However, once this happens, the Navigation Controller Bar still appears on the top and you can still navigate back into the setup menu, which is not at all what I want. Here's what I mean:
The user shouldn't be able to access the initial setup after already setting up. My storyboard looks like this:
The left four boxes are for setup. The right three boxes is the 'Main Menu' I want to create.
As you can see, there's no arrow between the last setup Controller and the Tab Bar Controller because I performed that segue programmatically (as shown above). How do I get the Navigation Controller to stop affecting the rest of my View Controllers?
This happens because a segue under the hood makes use of the current UINavigationController, so it doesn't matter if you perform the segue programmatically or in the storyboards directly.
There a number of ways how to approach this. A very easy way to achieve it is to simply hide the back button in your WelcomeViewController using self.navigationItem.hidesBackButton = YES;.
However, this doesn't really solve your problem if you really want a separation between these view controllers and want to get rid of the UINavigationController that was responsible for your setup process. What I would actually recommend for that kind of situation is to use two different storyboards, one for your initial setup and one for the main part. Then you switch your UIWindow's rootViewController to a view controller from the other storyboard programmatically. This can be done in few lines of code and helps to separate your concerns and keep a better visual overview on your app's flow.
Let me know in the comments if you need further explanation :)
Switching storyboards can be achieved using the following code:
UIStoryboard *main = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *welcomeViewController = [main instantiateViewControllerWithIdentifier:#"WelcomeViewController"];
UIWindow *mainApplicationWindow = [[[UIApplication sharedApplication] delegate] window];
mainApplicationWindow.rootViewController = welcomeViewController;
But if you want to instantiate the whole UITabBarController you use in you r main interface, you'll have to give it an ID (e.g. MainTabBarController) in storyboards so that you can instantiate it using instantiateViewControllerWithIdentifier:. So then, instead of instantiating your WelcomeViewController, you'll go:
UIStoryboard *main = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UITabBarController *mainTabBarController = [main instantiateViewControllerWithIdentifier:#"MainTabBarController"];
UIWindow *mainApplicationWindow = [[[UIApplication sharedApplication] delegate] window];
mainApplicationWindow.rootViewController = mainTabBarController;
A very easy solution is to this problem, is to make a segue from your last UIVieController you want to be embedded in the UINavigationController to the one you want out of the UINavigationController.
Step 1.
Ctrl drag from the yellow icon on top of the UIViewController (embedded in the UINavigationController) to the UIViewController you want to segue to (out of the UINavigationController), and select "Present Modally"
Step 2.
Give the segue an identifier in storyboard
Step 3.
In your code, where you want to do the segue, put;
performSegue(withIdentifier: "yourSegueID", sender: self)
This will do the trick, and makes your storyboard more readable.

Storyboards - how to perform segues automatically without animation and showing the screen

I have an application where the user has to enter some information on the initial screens, such as the login credentials. All the navigations are done using a storyboard and segues, which automatically sets the navigation bar on top.
I have two storyboards that share the same controllers, so I use the same name for the segues (ipad and iphone versions).
So when the user comes back to the application, I read the core data and know that he has already performed the initial steps, so I would like to "skip" those screens.
Problem:
I can only execute the segues after the view is visible, otherwise the navigation is screwed up. But when doing so, the user sees the screen briefly and sees the animation "pushing" that screen away. I'd like to keep the navigation history on the navigation bar, that is why I want to use the segues and all the logic associated with them.
All the solutions point to creating the views programatically and putting them on the stack, but I'd like to take advantage of the storyboards.
Here is one way of doing it;
In your StoryBoard, assign an identifier to the second view controller (this is done on the identity inspector, setting the Storyboard ID field). In the code below, i have named mine secondVC;
Then in the viewDidLoad for your first controller (the one you want to skip but come back to) do something like this;
- (void)viewDidLoad{
[super viewDidLoad];
/// validate viewController one being displayed
if(dontDisplayFirstController){
UIStoryboard *storyBoard = self.storyboard;
UIViewController *targetViewController = [storyBoard instantiateViewControllerWithIdentifier:#"secondVC"];
UINavigationController *navController = self.navigationController;
if (navController) {
[navController pushViewController:targetViewController animated:NO];
}
}
}
This will efectivly push to the second viewController whilst still maintaining viewController one in the navigation tree.

After using presentViewController existing segues can't be found

I am returning to my login view using the code below. The view loads correctly and everything looks fine. All buttons work etc.
JALoginViewController *loginVC = [[JALoginViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:loginVC];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentModalViewController:navigationController animated:YES];
However, if a user tries to log in again, the segue that takes them to the next scene can't be found.
I'm using performSegueWithIdentifier if the users login credentials are correct, like this:
[self performSegueWithIdentifier:#"loginSegue" sender:self];
This is the error I receive:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<JALoginViewController: 0x8d614b0>) has no segue with identifier 'loginSegue''
I've done lots of searching on Google and through the docs for the solution to this, the closet I've got (at least I think) is this question. The explain and solution sound like they could be correct and relevant, but I can't put them into practice.
Documents I've read and tried:
initWithRootViewController
popToRootViewController - The current root view controller is for a tab bar - not the login scene I need so as far as I'm aware I can't use this.
popViewControl
pushViewControl - This works to an effect, I don't think it is the correct way though. I don't want there to be navigation bar and I don't want my tab bar to appear when the user returns to the login scene.
I've tried various methods with limited / no effect. At this stage any help would be greatly appreciated.
Please let me know if I haven't provided enough information.
Thanks
JA
Edit - Zoomed out image of storyboard
![Zoomed out image of Storyboard][1]
On the basis of the screen snapshot of the revised question, from your rightmost red-highlighted scene, you should be able to:
[self.tabBarController dismissViewControllerAnimated:YES completion:nil];
and you'll be back at that initial screen (I'm assuming you did modal segue from initial screen to your tab bar controller).
Original answer:
If you want to manually push to a view controller, rather than creating it via alloc/init, you should use
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"loginsSceneStoryboardIdHere"];
or, if that view controller was the "initial" scene (the one with the simple arrow coming in from the left), you could use
UIViewController *controller = [self.storyboard instantiateInitialViewController];
And you shouldn't be manually creating the navigation controller, either. If the loginVC needs a navigation controller, you should embed that scene in a navigation controller right in the Interface Builder, then give that new navigation controller its own unique storyboard identifier, and then you can
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"navControllerSceneStoryboardIdHere"];
[self presentViewController:controller animated:YES completion:nil];
I must confess that I'm worried by this whole "return to login via pushViewController" construct. I assume you know that you're not "returning" to it, but creating a new copy of it. If you push/modal from A to B and then B to C and then C to A, you're holding 4 views and their controllers in memory, two copies of A and one of B and one of C (which is, obviously, not good). I just wanted to make sure you don't have a circular set of segues and/or push/presentViewController references.
If the login is the initial scene in your app and if you've been doing only push segues (no modal segues along the way), you can do a:
[self.navigationController popToRootViewControllerAnimated:YES];
That will take you to the top level view controller, and it will pop off and release all of the intervening scenes.
If you're using iOS 6, you can avail yourself of the unwind segue, which can achieve the same functionality, but it doesn't care whether the preceding segues were pushes or modals.
There are lots of ways of skinning the cat, but generally doing a new presentViewController to the first scene in your storyboard is a very bad idea.

Moving from one view to another iOS

I'm relatively new to iOS development. I am to move from one viewController to another I use a modal segue transition on button click. This is a game so i want to allow the user to click images to essential move the the app menus.
I have a main page that displays several images, on clicking one i want to be able to move to another view. Currently doing this with a modal segue is causing odd problems with my touchesEnded event where if, for example, i navigate to a page 3 times the touchesEnded event is fired 3 times.
Is there a better way for me to do this or am i just missing thing fundamental?
Thanks
Yes, I think you must make the the navigation controller your root view controller then push views accordingly
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:YOUR_BASE_CONTROLLER]
self.rootViewController = nav;
this is in your app delegate.
Then in your action method
[self.navigationController pushViewController:secondViewController animated:YES]
Im assuming you are using the Storyboard to link VCs using segues.
Modal segues are great for simple transitions but really seem to limit what you can accomplish when they are just linked through SB. Ive found that creating an IBAction that includes the following for a VC segue will allow you to not only control your segues more efficiently but also allow you to have a clearer view of what is actually occurring during the transition.
-(IBAction)goToVc:(id)sender{
//Other code to take place during the segue here
//This will identify the Storyboard in use
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
//This will identify the View Controller to switch to
SecondViewController *vc2 = [storyboard instantiateViewControllerWithIdentifier:#"SecondViewControllerID" ];
[self presentViewController:vc2 animated:YES completion:NULL];
}
Every time you perform a modal segue, you lose the UINavigationController that you were previously using. What you need to do is embed a UINavigationController in the view that you are performing a modal segue to.
Check out a question I answered the other day to help you visualize more clearly what I'm talking about.

Resources