BarButton item with different behaviours - ios

In my current app, I have three UIViewControllers and I am using StoryBoard:
ViewController, MenuViewController, CategoriasListViewController.
To show MenuViewController from ViewController, I am using a button action.
To go back from MenuViewController to ViewControler, I am using a BarButton item with the following action method:
- (IBAction)backButton:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
To show CategoriasListViewController from MenuViewController, I am using also a button action.
To go back from CategoriasListViewController to MenuViewControler, I am using a BarButton item with the following action method (the same as above):
- (IBAction)backButton:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
But in this case, MenuViewController is not shown, the BarButton action is ignored.
I am using the customised BarButton actions instead the default Back button, because in the header background I have an image with a logo that would be overlapped by the default Back button.
Any help is welcome. Thank you.
UPDATE 1: This is my StoryBoard:

I have difficulties reading your story board but I'm pretty sure that if you put a break point in your method - (IBAction)backButton:(id)sender you will hit that break point.
I'm pretty sure the method is being call when you hit the button (unless you forgot to hook it).
The problem look like you are wrapping your CategoriasListViewController inside a Brand NEW Navigation controller. So when -backButton: get call you are talking to that new NavigationController instead of the original one that you really want to talk to.
Remove that second unnecessary UINavigationController and it should work.

Related

Segue from screen with several buttons?

I am trying to write an app using UINavigationViewController. My first screen has several buttons on it, and on the click of each button, I want to segue to a UIViewController. I know that I can add a segue on each button, all pointed to the UIViewController that I want to go to, but I was wondering if it is possible to use only one segue that can be fired from each of the buttons.
If that is not possible, I was wondering if it was possible to open the second UIViewController from the first one, on button click, and provide a Back button like the UINavigationView provides. I did manage to get everything on this idea working, except for the back button. I mean I can put a standard button somewhere on the screen and go back, but I'd like the standard back button on the UINavigationView.
Phew! I'm not sure if that makes any sense.
I know that I could also use a tableview, but I'm trying to set this up with buttons.
Thanks
Edit: Thank you to everyone that answered. I now have this working. I would vote up the answers, but I don't have enough posts to do it. I appreciate the answers!
If you need to have separate action functions for each button, suggest that you segue from the main controller to the other controller and create a segue identifier (see xcode procedure below); then, use performSegueWithIdentifier from each of the button action functions. You can also take advantage of the prepareForSegue. To create the segue, control-drag from the left button in the controller in the storyboard to the controller you want to segue to and pick show.
Check the example code in swift that I did for a very similar problem in the SO reference
Linking View Controllers through button
You can embed the main controller in a navigation controller and that will give you the ability to navigate back. If you have multiple layers you can also use unwind segue.
Link each button to one single action (ex. buttonClick) in that ViewController and then perform the appropriate segue using pushViewController method on self.navigationController
-(IBAction)buttonClick:(id)sender {
if(sender.id == self.button1) {
DestinationViewController *vc = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:#"VC_IDENTIFIER"];
[self.navigationController pushViewController:vc animated:YES];
}
Or if you already have that 1 segue defined in storyboards you can use
[self performSegueWithIdentifier:#"SegueIdentifier" sender:self];
And use that inside the buttonClick method. Using the 1st example, or the second one as long as the segue you setup in the storyboards is a push then you should already get the back button as that is the default behavior for pushing view controllers onto the navigation stack.

Return values to presenting view controller when navigation back button pressed

I'm having trouble piecing this all together. I have a view controller that opens up another (pushes it on to the navigation stack). On that presented view controller, the user enters a value in a text view. When the user pushes the back button in the navigation, I want to be able to pass the value that they entered in the text view back to the presenting controller.
I've looked for a way to use unwind segue with the back button but haven't found anything. When I create my back button (programmatically) I use initWithTitle:style:target:action but I'm not sure how in implementing the action method that I'll be able to access the value set in the presented controller. Might have to use a delegate to link the two, but not sure of the exact integration point for this scenario.
I feel like I'm so close here and a little help would get me there. Thanks!
The two most common models to use for this interaction are for the child view controller to have either a delegate or a completion block. Either would be set in the prepareForSegue method. My personal preference is the completion block method just because it keeps code contained, but ymmv.
There are also multiple models for detecting when your child view controller is dismissed and you need to invoke the delegate and/or completion:
Use a custom back button. Not a fan of this as it can be an issue to create a back button that really looks and acts like the Apple original, especially if supporting iOS 6 and iOS 7.
Hook viewDidDisappear and see if you're still in the navigation controller's viewControllers array. This is better as the back button works right, but it still feels kind of hokey.
Use the UINavigationBarDelegate method navigationBar:shouldPopItem: This is attractive, especially if you have other validation that needs to happen like checking for saved/unsaved values. To implement this you'll have to subclass UINavigationController and forward the method to your child view controller.
EDIT: Details on Option 2:
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if(![self.navigationController.viewControllers containsObject:self])
{
// We're not still in the navigation stack so we must've been
// popped. If we were pushed, viewDidDisappear would be called
// but viewControllers containsObject:self would be true
}
}
EDIT: Clarified Option 3: in your navigation controller subclass
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController* top = self.topViewController;
if([top respondsToSelector:#selector(navigationBar:shouldPopItem:)])
return [(id)top navigationBar:navigationBar shouldPopItem:item];
return [super navigationBar:navigationBar shouldPopItem:item];
}
Then you can implement navigationBar:shouldPopItem: in the classes that need the functionality.
the back button does not actually comes up with any event associated with itself so that you can pass the values between the previous and to be Popped ViewController.
You would have to implement Delegate pattern to pass values. In this case as you cant catch when backButton is pressed, you need to use custom leftBarButtonItem or use a image with < in itself.

Segue to home view

I am trying to segue back to a home screen and pop everything from the stack (of views?)
I am using UINavigationController:
[self.navigationController popToRootViewControllerAnimated:YES];
Where shall I call this method? I have tried in -(void) prepareForSegue which does not work.
Basically I want to make sure when I go back to home screen there is no back button on the left of the title.
Home screen here refers to the welcome screen of my App.
I am not sure which part of my code shall be posted. Please comment if you need to see my code.
Thank you in advance.
Assuming you're using a Storyboard, and a UIButton declared over there is the one that triggers that action you wanna perform, you should declare an IBAction: -(IBAction)buttonPressed:(id)sender; in the .h and implement it in the .m. Inside that method should appear that [self.navigationController popToRootViewControllerAnimated:YES]; code. Also remember to attach that action to that UIButton when touch up inside in the storyboard.
Hope it helped!
I think your confusion is around the concepts of what a segue is and how it relates to the navigation controller stack. The stack is exactly the same as the basic data structure stack (http://en.m.wikipedia.org/wiki/Stack_(abstract_data_type)) in that it's a last-in-first-out store. When you segue, you're normally adding to that stack, e.g. Pushing a new VC onto the stack. A back button in most cases would not be doing that. It would be popping, or removing from the stack. Which is why you probably wouldn't call pop methods from the prepareForSegue method. Instead try calling it at some other point, like when they tap the back button, or after some action has taken place.

Move to start of storyboard from button on last screen

I'm trying to get a storyboard to bring the user back to the first screen of a storyboard when a button is pressed.
I have the button click hooked up and the method is hit when it's tapped but it's either doing nothing or crashing the app depending on what I've got in that method (I've tried so many things at this stage that I can't remember the original setup)
The best I can achieve is that once tapped the user gets brought to the third screen in the storyboard, rather than the first.
Here's the storyboard, I want to get the user to move from the button circled in red back to the very first view controller (top left of screenshot).
Maybe the storyboard layout will help people, I'll post some of the various methods I've tried as well.
Here's the method
- (IBAction)done:(id)sender
{
//Multitude of attempts have been in here, either they don't do
//anything or they just send the user back to the NEW REPORT screen.
//Button does nothing in these following events
[self.navigationController popToRootViewControllerAnimated:YES];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
}
If you want to start fresh, have the button instantiate a new instance of the first controller, and set it as the window's root view controller:
FirstViewController *first = [self.storyboard instantiateViewControllerWithIdentifier:#"First"];
self.view.window.rootViewController = first;
It's not clear to me exactly how you're getting to that controller with the button in question, but you might want to put dealloc methods in all your controllers with a log to see if all are getting deallocated at some point in your navigation, and when you go back to the new first controller as I've outlined above.

Fire a segue conditionally to multiple View Controllers

After struggling for days on firing a segue conditionally, I managed to solve it thanks to Simon's answer here. Please take a moment to have a look or you might not understand what I'm talking about below. I didn't copy paste his answer because he's already explained it nicely over there.
Now I've faced a new question. What if I have multiple View Controllers that I want to segue to from one View Controller?
To explain it further : Say I have one MainViewController with 2 buttons. When clicked upon each button, it should segue to their respective View Controller. First button to FirstViewController and the second button to SecondViewController.
The method described in Simon's answer can be used when you segue from one View Controller to another View Controller. Since in that method, you tie the segue to the View Controller itsrlf and not to the button, you have only one segue with an identifier for that particular View Controller. Therefore I cannot distinguish between the button taps separately.
Is there a workaround to solve this problem?
Thank you.
It might be bit premature to say this but I guess you should look into Segue more deeply.
Yes you can perform segure from button. Just control click the button and drag the cursor to view controller you want it SEGUE'd. And from my understanding only condition there is each button tap results a segue to a fixed view. There is no condition there.
Also, you can push the navigation controller manually by
YourViewController *destViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"YourDestinationViewId"];
[self.navigationController pushViewController:destViewController animated:YES];
UPDATE:
prepareForSegue is too late to stop a segue from proceeding. Yes you can create multiple segues from your view to other view controllers. And in this case you have to do so. Don't reate a segue from button, just define a IBACtion on the button click you can do the validation from there,
if(validationSuccess) {
[self performSegueWithIdentifier:#"segue1" sender:self];
}
if you are using ios6
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
return YES on validation success and NO on failure to stop it from proceeding.
I suggest you look a bit at reworking your code logic.
If I understand correctly, you have a VC (embedded in a Nav. Controller) with 2 buttons and you have figured out how to segue each button to a different VC.
Your problem is you want to make sure that even if one of the buttons are pressed, a validation is done before an action takes place. I would advise this is bad User Interface design because the user has the illusion that this button might do something and then they click it and nothing happens.
UIButton can be connected to IBActions (to initiate actions) and IBOutlets (to set their properties). If this is a button created in IB directly, I would connect it to your class as an Outlet property:
#property (nonatomic,weak) IBOutlet UIButton* myButton;
And then set its enabled value:
self.myButton.enabled=NO;
This will keep the button and dim it. This is much better UI design and the user knows they should not press the button because some condition is not satisfied.
I would rework the code so that you set this value as disabled by default for example and enable it appropriately in your code whenever your "condition" is satisfied.
Obviously if this button is created programmatically (in your code without IB) then it is easy to just use the second command above.
Hope this helps.
I just wrote another way to call multiple detail views from a single table. Each cell could essentially make a different view be displayed. The code is similar to what you see in this post but you essentially use identifiers and attributes on the list item to determine which view to show.
https://codebylarry.com/2016/07/15/multiple-detail-views-in-swift/
override func tableView(tableView: UITableView,didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 1 {
self.performSegueWithIdentifier("secondView", sender: self)
} else {
self.performSegueWithIdentifier(“others", sender: self)
}
}

Resources