Best way to push additional UIViewControllers onto a UINavigationController - ios

I'm working with the Master-Detail project template that comes with Xcode and referenced in http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/SecondiOSAppTutorial/
Problem: I am trying to figure out how to add additional UIViewControllers to the default UINavigationController that this template comes with.
Specifically, I would like to add a DetailEditViewController after DetailViewController. Here is what I've done to this effect so far:
In DetailViewController I added an edit button to the navigationItem:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit
target:self
action:#selector(editDetailItem:)];
[self configureView];
}
You can see it specifies a message selector editDetailItem:, which I've implemented as:
- (void)editDetailItem:(id)sender
{
[self.navigationController pushViewController:
[[DetailEditViewController alloc] init] animated:YES];
}
I've created a DetailEditViewController on the Storyboard, and the code runs without crashing, producing a black, blank window with a navigation item to take me back to detail. From here on I am pretty confused:
When I drag a new View Controller to the Storyboard, no corresponding code files are created! Am I responsible for making code files for these controllers? I see that Storyboard View Controllers are associated with a Class in the Identity Inspector... but why on earth would it not create templates for a new UIViewController when I drag one onto the Storyboard?
Should I be using a seque instead of -pushViewController to get from DetailViewController to DetailEditViewController? If so, I'm not sure how to add one on the Storyboard, because the navigationItem's UIBarButtonItems are all added in-code. There's nothing to Ctrl-drag from.
How do I send information from DetailViewController to DetailEditViewController? When MasterViewController segues to DetailViewController, it specifies the sender via - prepareForSegue:sender:

You're right, no corresponding files are produced. How is the system supposed to know what class you want? You need to create a UIViewController subclass, and change the class of the controller you drag in, to that class. The easiest way to push the new controller is to use a push segue -- if you don't have a UI element in the storyboard to connect that to, you connect it directly from the controller and give the segue an identifier (which I call "GoToEdit" in my example). In the action method for the edit button, then perform the segue:
[self performSegueWithIdentifier:#"GoToEdit" sender:self];
If you want to pass information, then you implement prepareForSegue:, something like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"GoToEdit"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSDate *object = _objects[indexPath.row];
[[segue destinationViewController] setDetailItem:object];
}
}
It's a good thing to check the segue identifier first. Then you can access your destinationViewController (you might have to cast it to your class, so the compiler will recognize any property of it you're trying to set), and pass what you want to it.

Related

Segue is executed twice

I am trying to transition between view controllers with a segue. It is navigating correctly but it is running twice.
Here is my code:
- (IBAction)onActionNext:(id)sender {
if ([[nameField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]isEqualToString:#""]) {
}else if([[emailField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:#""]){
}else if([[mobileField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:#""]){
}else{
[self performSegueWithIdentifier:#"VerifyNext" sender:sender];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"VerifyNext"]) {
// Get destination view
OTPSceneViewController *vc = [segue destinationViewController];
[vc setTitle:#"myapp"];
}
}
If even after commenting this line segue still run then as #mikealter said segue is connected your button. Check if when you check for when you click segue what it highlights.
A button like:
Or A View Controller like:
If you fall in first case that means your segue is connected to a button and, that is the reason why it opens next page even if you comment your code. And 2 times when you keep code(one from storyboard and another from code).
Edit:
If you want to call next page from code only. Remove Segue. Then reconnect it with view controller like:
First, whenever you create a segue (i.example) FROM a Button TO a View Controller, you are creating the link between VC1 and VC2.
If you run your project you will see that the segue works navigating from one VC to the other one.
This means that it already performs the segue, so if you declare an ACTION for your button and make a call to - perform segue... - it will run twice.
Note: if you call performSegueWithIdentifier is probably because you want to pass some data via SENDER. In that case you (obviously) need to call the segue ONCE, so instead of creating the segue from the button to the next VC, (first delete that segue), create a new one FROM the VCOrigin to VCDestination -> there are many ways to do it, but from the Interface Builder is as simple as right click on the top yellow rounded icon and drag&drop on the destinationVC. Don't forget to set the identifier on the Attributes Inspector.
Now, in your button~Action make the call to performSegueWithIdentifier and pass the data.
if you stop storyboard segue use following method:
override func shouldPerformSegueWithIdentifier(identifier: String,sender: AnyObject?) -> Bool {
return false
}
You don't even add
[self performSegueWithIdentifier:#"VerifyNext" sender:sender];
in onActionNext method if you add the segue in storyboard
If you want to navigate to next view controller use above line.
Preparing for the Segue
In order to pass information from one view controller to another using
the UIStoryboardSegue object we’ll need to take advantage of the
current view controller’s prepareForSegue:sender: method.
But If you call both,it calls two times.So make sure that whether you want to pass information or data to next view controller or just navigate the view.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"prepareForSegue: %#", segue.identifier);
if([segue.identifier isEqualToString:#"VerifyNext"])
{
VerifyNextViewController *verifyNVC = segue.destinationViewController;
verifyNVC.name = #"Prashabt";
verifyNVC.id = #"1234";
}
else if([segue.identifier isEqualToString:#"VerSeg"])
{
AnotherViewController *anotherVC = segue.destinationViewController;
anotherVC.name = #"Sharma";
anotherVC.id = #"3245";
}
}
have a look of this question this may sure rectify
or there is another way to navigate to another view controller using below code.
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier:#"viewControllerName"];
[self.navigationController pushViewController:vc animated:YES];
make sure you give identity to the view controller using identity inspector see image.

Using pushViewController is very slow on iPad

I'm trying to move from one UIViewController to another using -pushViewController:animated using the below snipped :
SomeController *tabBar = [self.storyboard instantiateViewControllerWithIdentifier:#"mycus"];
[self.navigationController pushViewController:tabBar animated:YES];
Moving from one UIViewController (which has no UIViews or images) has a 3 second delay before moving. What is causing this issue and how would I solve it?
For storyboard, click on the segue you have created, give it an identifier like "someSegue".
Next you will want to trigger the prepareForSegue delegate in your viewcontroller doing the following...
[self performSegueWithIdentifier:#"SomeSegue" sender:nil];
..Then you will want to include the delegate method in your view controller and do any preparing you might want before the next view controller is initialised.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
SomeController *vc = [segue destinationViewController];
// Here you will want to do your prep e.g. pass iVars etc from one controller to the other.
}

Segue to a UINavigation Controller programmatically without storyboards

I have code that uses Storyboards for seques, like so:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowDiagnosis"])
{
[segue.destinationViewController setHappiness:self.diagnosis];
}
...
But I want to do it programmatically. I have myViewController class and when I click on a button I want to animate and push to myUINavigationController.
How is this done programmatically?
First things first, a segue cannot be created programmatically. It is created by the storyboard runtime when it is time to perform. However you may trigger a segue, which is already defined in the interface builder, by calling performSegueWithIdentifier:.
Other than this, you can provide transitions between view controllers without segue objects, for sure. In the corresponding action method, create your view controller instance, either by allocating programmatically or instantiating from storyboard with its identifier. Then, push it to your navigation controller.
- (void)buttonClicked:(UIButton *)sender
{
MyViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"my-vc-identifier"];
// OR MyViewController *vc = [[MyViewController alloc] init];
// any setup code for *vc
[self.navigationController pushViewController:vc animated:YES];
}
First of all segue can be used only with if you have a UINavigationController that will handle the navigation (push, pop, etc).
So if you have a UINavigationController and you want to push another UIViewController on the stack without using segue then you can use pushViewController:animated: method which also has a reverse popViewControllerAnimated:. Also UINavigationController provides other methods for adding/removing UIVIewControllers, for more info check UINavigationController class reference.

Creating a segue programmatically

I have a common UIViewController that all my UIViewsControllers extend to reuse some common operations.
I want to set up a segue on this "Common" UIViewController so that all the other UIViewControllers inherit.
I am trying to figure out how do I do that programmatically.
I guess that the question could also be how do I set a segue for all my UIViewControllers without going into the story board and do them by hand.
I thought I would add another possibility. One of the things you can do is you can connect two scenes in a storyboard using a segue that is not attached to an action, and then programmatically trigger the segue inside your view controller. The way you do this, is that you have to drag from the file's owner icon at the bottom of the storyboard scene that is the segueing scene, and right drag to the destination scene. I'll throw in an image to help explain.
A popup will show for "Manual Segue". I picked Push as the type. Tap on the little square and make sure you're in the attributes inspector. Give it an identifier which you will use to refer to it in code.
Ok, next I'm going to segue using a programmatic bar button item. In viewDidLoad or somewhere else I'll create a button item on the navigation bar with this code:
UIBarButtonItem *buttonizeButton = [[UIBarButtonItem alloc] initWithTitle:#"Buttonize"
style:UIBarButtonItemStyleDone
target:self
action:#selector(buttonizeButtonTap:)];
self.navigationItem.rightBarButtonItems = #[buttonizeButton];
Ok, notice that the selector is buttonizeButtonTap:. So write a void method for that button and within that method you will call the segue like this:
-(void)buttonizeButtonTap:(id)sender{
[self performSegueWithIdentifier:#"Associate" sender:sender];
}
The sender parameter is required to identify the button when prepareForSegue is called. prepareForSegue is the framework method where you will instantiate your scene and pass it whatever values it will need to do its work. Here's what my method looks like:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"Associate"])
{
TranslationQuizAssociateVC *translationQuizAssociateVC = [segue destinationViewController];
translationQuizAssociateVC.nodeID = self.nodeID; //--pass nodeID from ViewNodeViewController
translationQuizAssociateVC.contentID = self.contentID;
translationQuizAssociateVC.index = self.index;
translationQuizAssociateVC.content = self.content;
}
}
I tested it and it works.
By definition a segue can't really exist independently of a storyboard. It's even there in the name of the class: UIStoryboardSegue. You don't create segues programmatically - it is the storyboard runtime that creates them for you. You can normally call performSegueWithIdentifier: in your view controller's code, but this relies on having a segue already set up in the storyboard to reference.
What I think you are asking though is how you can create a method in your common view controller (base class) that will transition to a new view controller, and will be inherited by all derived classes. You could do this by creating a method like this one to your base class view controller:
- (IBAction)pushMyNewViewController
{
MyNewViewController *myNewVC = [[MyNewViewController alloc] init];
// do any setup you need for myNewVC
[self presentModalViewController:myNewVC animated:YES];
}
and then in your derived class, call that method when the appropriate button is clicked or table row is selected or whatever.
I've been using this code to instantiate my custom segue subclass and run it programmatically. It seems to work. Anything wrong with this? I'm puzzled, reading all the other answers saying it cannot be done.
UIViewController *toViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"OtherViewControllerId"];
MyCustomSegue *segue = [[MyCustomSegue alloc] initWithIdentifier:#"" source:self destination:toViewController];
[self prepareForSegue:segue sender:sender];
[segue perform];
Guess this is answered and accepted, but I just would like to add a few more details to it.
What I did to solve a problem where I would present a login-view as first screen and then wanted to segue to the application if login were correct. I created the segue from the login-view controller to the root view controller and gave it an identifier like "myidentifier".
Then after checking all login code if the login were correct I'd call
[self performSegueWithIdentifier: #"myidentifier" sender: self];
My biggest misunderstanding were that I tried to put the segue on a button and kind of interrupt the segue once it were found.
You have to link your code to the UIStoryboard that you're using. Make sure you go into YourViewController in your UIStoryboard, click on the border around it, and then set its identifier field to a NSString that you call in your code.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle:nil];
YourViewController *yourViewController =
(YourViewController *)
[storyboard instantiateViewControllerWithIdentifier:#"yourViewControllerID"];
[self.navigationController pushViewController:yourViewController animated:YES];
For controllers that are in the storyboard.
jhilgert00 is this what you were looking for?
-(IBAction)nav_goHome:(id)sender {
UIViewController *myController = [self.storyboard instantiateViewControllerWithIdentifier:#"HomeController"];
[self.navigationController pushViewController: myController animated:YES];
}
OR...
[self performSegueWithIdentifier:#"loginMainSegue" sender:self];
well , you can create and also can subclass the UIStoryBoardSegue . subclassing is mostly used for giving custom transition animation.
you can see video of wwdc 2011 introducing StoryBoard. its available in youtube also.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIStoryboardSegue_Class/Reference/Reference.html#//apple_ref/occ/cl/UIStoryboardSegue
I'd like to add a clarification...
A common misunderstanding, in fact one that I had for some time, is that a storyboard segue is triggered by the prepareForSegue:sender: method. It is not. A storyboard segue will perform, regardless of whether you have implemented a prepareForSegue:sender: method for that (departing from) view controller.
I learnt this from Paul Hegarty's excellent iTunesU lectures. My apologies but unfortunately cannot remember which lecture.
If you connect a segue between two view controllers in a storyboard, but do not implement a prepareForSegue:sender: method, the segue will still segue to the target view controller. It will however segue to that view controller unprepared.
Hope this helps.
Storyboard Segues are not to be created outside of the storyboard. You will need to wire it up, despite the drawbacks.
UIStoryboardSegue Reference clearly states:
You do not create segue objects directly. Instead, the storyboard
runtime creates them when it must perform a segue between two view
controllers. You can still initiate a segue programmatically using the
performSegueWithIdentifier:sender: method of UIViewController if you
want. You might do so to initiate a segue from a source that was added
programmatically and therefore not available in Interface Builder.
You can still programmatically tell the storyboard to present a view controller using a segue using presentModalViewController: or pushViewController:animated: calls, but you'll need a storyboard instance.
You can call UIStoryboards class method to get a named storyboard with bundle nil for the main bundle.
storyboardWithName:bundle:
First of, suppose you have two different views in storyboard, and you want to navigate from one screen to another, so follow this steps:
1). Define all your views with class file and also storyboard id in identity inspector.
2). Make sure you add a navigation controller to the first view. Select it in the Storyboard and then Editor >Embed In > Navigation Controller
3). In your first class, import the "secondClass.h"
#import "ViewController.h
#import "secondController.h"
4). Add this command in the IBAction that has to perform the segue
secondController *next=[self.storyboard instantiateViewControllerWithIdentifier:#"second"];
[self.navigationController pushViewController:next animated:YES];
5). #"second" is secondview controller class, storyboard id.
I reverse-engineered and made an open source (re)implementation of UIStoryboard's segues: https://github.com/acoomans/Segway
With that library, you can define segues programmatically (without any storyboard).
Hope it may help.
A couple of problems, actually:
First, in that project you uploaded for us, the segue does not bear the "segue1" identifier:
no identifier
You should fill in that identifier if you haven't already.
Second, as you're pushing from table view to table view, you're calling initWithNibName to create a view controller. You really want to use instantiateViewControllerWithIdentifier.
Here is the code sample for Creating a segue programmatically:
class ViewController: UIViewController {
...
// 1. Define the Segue
private var commonSegue: UIStoryboardSegue!
...
override func viewDidLoad() {
...
// 2. Initialize the Segue
self.commonSegue = UIStoryboardSegue(identifier: "CommonSegue", source: ..., destination: ...) {
self.commonSegue.source.showDetailViewController(self.commonSegue.destination, sender: self)
}
...
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 4. Prepare to perform the Segue
if self.commonSegue == segue {
...
}
...
}
...
func actionFunction() {
// 3. Perform the Segue
self.prepare(for: self.commonSegue, sender: self)
self.commonSegue.perform()
}
...
}

Passing data from one view controller to another; iOS <=4 vs iOS 5

First, a little background. I'm new to iOS development, I've been in .Net land for a long time, and that's probably why I'm even asking this question, but here goes.
The basic setup is this. You have a UINavigationController with a RootViewController we'll call MasterViewController. When some action happens on this MasterViewController, we want to drill into a DetailsViewController. However, we also want to pass some data to the DetailsViewController.
It is my understanding, that in previous versions of the SDK (prior to iOS 5) the approach was similiar to this:
#implementation MasterViewController
-(IBAction)someAction
{
DetailsViewController *dvc = [[DetailsViewController alloc]initWithNibName:#"DetailsView" bundle:nil];
dvc.someDataProp = [self getSomeDataSomeHow];
[[self navigationController] pushViewController:dvc animated:YES];
}
#end
Now however, in iOS 5, it seems that this is now done using the Storyboard and segues. In XCode you set up the segue from the MasterViewController to the DetailsViewController, and then in code you do something like this:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
[segue.destinationViewController setSomeDataProp:[self getSomeDataSomeHow]];
}
My question is basically this: The older approach somehow feels a lot cleaner to me. You're being very explicit about the type of ViewController you're pushing on to the navigation stack and you can set properties easily on it. In the new approach though, destinationViewController is of type id (for obvious reasons), and it just feels a lot less clean to me. Again, this could be my .Net side coming out, but is this common in iOS? Just use id and throw caution to the wind?
With Storyboards you can assign a named identifier to the segue,
Select the segue and in the Attribute inspector you can add a name to the segue Identifier.
And in the prepareForSegue method you should check for this Identifier and thus you will explicitly know which segue is about to be performed and what the destinationViewController will be.
if ([segue.identifier isEqualToString:#"My First Segue Identifier"])
{
DetailsViewController *dvc = (DetailsViewController *) segue.destinationViewController;
// Set the DVC's properties
}
In many cases, the destination view controller for a segue may be a UINavigationViewController, and in that case, the solution (a slight modification of Dennis Mathews' solution above) will need to use the message"topViewController":
if ([segue.identifier isEqualToString:#"My First Segue Identifier"])
{
NavitationViewController* navController = [segue destinationViewController];
DetailsViewController *dvc = (DetailsViewController*) [navController topViewController]
// Set the DVC's properties
}
I haven't been working with iOS that long, but I've seen a few examples where you don't have to cast because of objective-c's loose coupled messaging system.
Instead of checking the segue identifier or casting to a specific ViewController you can do this:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController respondsToSelector: #selector(setCompany:)]) {
[segue.destinationViewController performSelector: #selector(setCompany:) withObject: self.company];
}
}
In the first line I ask if the destinationViewController has a method setCompany (if you have a property named company this one would be generated for you). If it does, you can call that method/set that property with the second line of code.
So in this case you don't really have to know the destination ViewController and could easily replace it with a different one that supports handling Companies.

Resources