I have a tableviewController with a bunch of buttons on it, and when they are tapped I want to push to a new view controller, and I am using the prepareForSegue method and then using control drag in the story board.
In prepareForSegue I have
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showItems"]) {
}
And then a bunch of stuff that I am sending to the next view controller, so in the storyboard all of my buttons have the same identifier "showItems" which is giving me a warning of"Multiple segues have same identifer showItems" What is the best way to get rid of this warning? I still want all the buttons to do the same thing, is the a better practice for pushing the next viewController.
Thanks for the help in advance.
EDIT
- (IBAction)showItems:(id)sender{
[self performSegueWithIdentifier:#"showItem" sender:self];
}
I have connected all the buttons to the IBAction, but then where would I pass the data to the next view controller?
In PrepareForSegue I have
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIButton *senderButton=(UIButton *)sender;
NSInteger meal = senderButton.tag % 10;
}
But this throws exception unrecognized selector sent to instance 0x7fcc1a496880
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DiningMealsTableViewController tag]: unrecognized selector sent to instance 0x7fcc1a496880'
You can create a single segue using control drag from the tableviewController to the other viewController and fire the segue in the IBAction of the button. You can create only one IBAction for all buttons and use the sender parameter to recognize the button.
You only need one segue identifier, which identifies the movement from one UIViewController to another. Then inside the "calling" UIViewcontroller (the UIViewController that owns the tableview and buttons) you can trigger that Segue with a custom function that each of your buttons will call.
Like this:
- (void)viewDidLoad {
[super viewDidLoad];
// Create the buttons and assign them unique tags
// and assign them a custom click handler
UIButton *button1 = [[UIButton alloc] initWithFrame:button1Frame];
button1.tag = 1;
[button1 addTarget:self action:#selector(handleButtonPress:) forControlEvents:UIControlEventTouchUpInside];
UIButton *button2 = [[UIButton alloc] initWithFrame:button2Frame];
button2.tag = 2;
[button2 addTarget:self action:#selector(handleButtonPress:) forControlEvents:UIControlEventTouchUpInside];
// Add the buttons to the view or to your UITableViewCell
[self.view addSubview:button1];
[self.view addSubview:button2];
}
- (void)handleButtonPress:(id)sender {
// get the tag you assigned to the buttons
// to identifiy which one was pressed
UIButton *btn = sender;
int tag = btn.tag;
NSLog(#"You clicked button with tag: %d", tag);
if (tag == 1){
NSLog(#"You clicked on Button1");
// Do something custom here.
} else if (tag == 2) {
NSLog(#"You clicked on Button2");
// Do something custom here.
}
// Now this is where all your buttons can call the same SegueIdentifier
[self performSegueWithIdentifier:#"showItems" sender:sender];
}
Obviously I don't know much about the structure of your application, and my example adds the buttons to the root view, in your app you would have this same setup, only attached to the buttons you placed in your UITableViewCell but this one way you can get away with having multiple buttons trigger the same seque without having to explicitly assign a seque identifier in multiple places. It has the added benefit of being able to perform custom functions for each button before calling -performSegueWithIdentifier:sender, allowing added flexibility to your app.
Hope this helps!
Related
I am creating custom dialogs for my app and some what copying UIAlertController in some aspects. How should I implement the behaviour where when you click any action from alert/dialog the controller is dismissed.
How does Apple do it without making us manually specify for each action handler that it should dismiss the view controller?
I have like them one view controller class:
#interface MyAlertViewController : UIViewController
- (void)addAction:(MyAlertAction *) action;
//...
And one class for the actions:
#interface MyAlertAction : NSObject
- (instancetype)initWithTitle:(nullable NSString *)title handler:(void (^)(MyAlertAction *action))handler;
EDIT: How I did it taking in accord the answer feedback:
//MYAlertViewController.m
- (void)viewDidLoad {
for (int i = 0; i < self.actions.count; i++) {
MYAlertAction *action = self.actions[i];
button = [[UIButton alloc] initWithFrame:CGRectZero];
button.tag = i;//this here is how I link the button to the action
[button addTarget:self action:#selector(executeAction:) forControlEvents:UIControlEventTouchUpInside];
[actionStackView addArrangedSubview:button];
[self.actionsStackView addArrangedSubview:actionLayout];
}
}
- (void)executeAction:(UIButton *) sender{
[self dismissViewControllerAnimated:YES completion:^{
//this is where the button tag comes in handy
MYAlertAction *actionToExecute = self.actions[sender.tag];
actionToExecute.actionHandler();
}];
}
How does Apple do it without making us manually specify for each action handler that it should dismiss the view controller?
You are confusing two different things:
The UIAlertAction's last initialization parameter, the handler parameter, which you get to set from outside, and which is to run after the button is tapped and after the alert has been dismissed. It is a block.
The actual button's action, which the client can't set or see. It is configured by the alert controller. It is a selector.
So now, you play the role of the UIAlertController. In your
- (instancetype)initWithTitle:(nullable NSString *)title handler:(void (^)(MyAlertAction *action))handler;
the client hands you the first action I mentioned, the block, and you store it for later execution. But the second action, the button action, the selector, is entirely up to you as you create the button in response to this call.
So as you configure the button, just configure it with a target/action pair that calls into a method of your view controller, just as for any button. In method, when called, the view controller dismisses itself, and in the completion handler of the dismissal, calls the block.
I have made a custom cell.In each custom cell there are many button like 3 to 4 i want add tap gesture for each button inside that cell.So that i can uniquely identify which cell's button was clicked.I have searched a lot but didn't find any good solution.
Please tell.
you want to access the button you can directly access, no need of Gesture
do like
[yourButton1 addTarget:self action:#selector(selectButtonPressed1:) forControlEvents:UIControlEventTouchUpInside];
yourButton1.tag=indexPath.row;
[yourButton2 addTarget:self action:#selector(selectButtonPressed2:) forControlEvents:UIControlEventTouchUpInside];
yourButton2.tag=indexPath.row;
[yourButton3 addTarget:self action:#selector(selectButtonPressed3:) forControlEvents:UIControlEventTouchUpInside];
yourButton3.tag=indexPath.row;
[yourButton4 addTarget:self action:#selector(selectButtonPressed4:) forControlEvents:UIControlEventTouchUpInside];
yourButton4.tag=indexPath.row;
Method - 1
-(void)selectButtonPressed1:(UIButton*)sender
{
NSLog(#"selcted button1");
}
Method - 2
-(void)selectButtonPressed2:(UIButton*)sender
{
NSLog(#"selcted button2");
}
Method - 3
-(void)selectButtonPressed3:(UIButton*)sender
{
NSLog(#"selcted button3");
}
Method - 4
-(void)selectButtonPressed4:(UIButton*)sender
{
NSLog(#"selcted button4");
}
Lets say you have n number of buttons. And you have one butotn action method:
YOu need to connect this method to all of your n buttons (touch up inside), so that whenever you press a button, this method will get hit.
You can either give tag values for your buttons or you can recognise them by thier title
-(IBAction) nButtonActions:(id)sender{
if([[sender titleLabel] isEqualtoString:#"your button-1 title"]){
//call your method for button -1
}
//or you can do the same using sender.tag
}
use restoration identifier.
button.restorationIdentifier = #"Cell Number _ button tag";
for example
button.restorationIdentifier = [NSString stringWithFormat:#"%#", indexPath.row];
NSString* rowIndexStr = ((UIButton*)sender).restorationIdentifier;
I have a UITableView that i would like to hide until the user taps the button searchButtonTapped. (I'm also using this button as an IBAction.)
Originally i'm hiding the table view as you see in the viewDidLoad, and i wanna show it after the button was tapped, but it does not shown up after i tap the search button. Do i missed something? For me, it seems it should be work properly, after the button was tapped i refresh the table view.
my .h file
#property (weak, nonatomic) IBOutlet UIButton *searchButtonTapped;
- (IBAction)searchButton:(id)sender;
.m file
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.hidden = YES;
}
- (void)buttonTapped:(id)sender {
if (sender == self.searchButtonTapped) {
self.tableView.hidden = NO;
[self.tableView reloadData];
}
}
- (IBAction)searchButton:(id)sender {
[self searchSetup];
}
It's impossible to tell from the little bit of code that you posted. Add NSLog statements in your buttonTapped method that show entering the method, entering the if statement, the value of searchButtonTapped, and the value of self.tableView.
Then you can tell if the method is getting called, if the if statement is evaluating as true, and if the table view is non-nil. One of those things is likely to be the cause of your problem.
I'm guessing that the if statement is wrong. what type is the property self.searchButtonTapped? Post the code that declares that property.
Based on the name I would guess that searchButtonTapped is a boolean?
you have declared only one IBAction, which is for the method searchButton.
This method call the searchSetup´s method. What is the purpose of it?
- (IBAction)searchButton:(id)sender {
[self searchSetup];
}
So you must have another IBAction for buttonTapped method witch is currently a "void" method and not a IBAction. Or you make that connection from the storyBoard, or you must declare it programaticly like:
[self.searchButtonTapped addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]
I have a UIViewController with a bunch of buttons that each have a (unique) tag. I wrote the following method:
- (void) highlightButtonWithTag: (NSInteger) tag
{
UIButton *btn = (UIButton *)[self.view viewWithTag: tag];
btn.highlighted = YES;
}
What I am trying to do is have a bunch of buttons that each function like a toggle: when I tap one, it should be come active (i.e. highlighted) and the one that was highlighted before should become "un"highlighted.
When the view comes up, I use the viewDidAppear method to set the initial selection:
- (void) viewDidAppear:(BOOL)animated
{
self.selectedIcon = 1;
[self highlightButtonWithTag: self.selectedIcon];
}
And this seems to work just fine: when the view comes up, the first button is selected. However, when I try to update stuff through the #selector connected to the buttons, the previous button is "un"highlighted but the button with sender.tag doesn't get highlighted.
- (IBAction) selectIcon:(UIButton *)sender
{
// "Un"highlight previous button
UIButton *prevButton = (UIButton *)[self.view viewWithTag: self.selectedIcon];
prevButton.highlighted = NO;
// Highlight tapped button:
self.selectedIcon = sender.tag;
[self highlightButtonWithTag: self.selectedIcon];
}
What am I missing here?
The problem is that the system automatically highlights then unhighlights the button on touchDown and touchUp respectively. So, you need to highlight the button again, after it's unhighlighted by the system. You can do by using performSelector:withObject:afterDelay: even with a 0 delay (because the selector is scheduled on the run loop which happens after the system has done it's unhighlighting). To use that method, you have to pass an object (not an integer), so If you modify your code slightly to use NSNumbers, it would look like this,
- (void) highlightButtonWithTag:(NSNumber *) tag {
UIButton *btn = (UIButton *)[self.view viewWithTag:tag.integerValue];
btn.highlighted = YES;
}
- (void) viewDidAppear:(BOOL)animated {
self.selectedIcon = 1;
[self highlightButtonWithTag: #(self.selectedIcon)];
}
- (IBAction) selectIcon:(UIButton *)sender {
// "Un"highlight previous button
UIButton *prevButton = (UIButton *)[self.view viewWithTag: self.selectedIcon];
prevButton.highlighted = NO;
// Highlight tapped button:
self.selectedIcon = sender.tag;
[self performSelector:#selector(highlightButtonWithTag:) withObject:#(self.selectedIcon) afterDelay:0];
}
i have a Navigation Bar, wich contains a Navigation Item, which contains 2 Bar Buttons, these are created in the Storyboard, and i wanted to change 1 of the buttons at runtime, now this works:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UINavigationItem *thisNavBar = [self myNavigationItem];
thisNavBar.rightBarButtonItem = nil; // this works, it gets removed
UIBarButtonItem *insertBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(insertSkemaItem:)];
thisNavBar.rightBarButtonItem = insertBtn; // this also works, it sets the btn
}
Now, in my other method, which is called by another controller, it does not work
- (void)callChildChange {
...
// remove old btn
UINavigationItem *thisNavBar = [self skemaNavigationItem];
thisNavBar.rightBarButtonItem = nil; // does not work?
}
There is nothing wrong with the method, it runs just fine, but the nav btn item does not get removed ?
skemaNavigationItem is a Navigation item, declared in the .h file which links the navigation item i made via the storyboard.
Your UI items need to be added to your code (by ctrl-dragging) in the header file (.h) so they can be publicly accessed from other classes/view controllers.
Presuming you've done this, hiding a UI item is best done by using
relevantClass.yourViewObject.hidden = YES;
or if you really need to delete it for good,
[relevantClass.yourViewObject.view removeFromSuperView];
Edits
Options for changing target method:
Declare #property (nonatomic, assign) BOOL myButtonWasPressed; and:
- (IBAction) myButtonPressed
{
if (!self.myButtonWasPressed)
{
// This code will run the first time the button is pressed
self.myButton.text = #"New Button Text";
self.myButtonWasPressed = YES;
}
else
{
// This code will run after the first time your button is pressed
// You can even set your BOOL property back, and make it toggleable
}
}
or
- (IBAction) myButtonWasPressedFirstTime
{
// do what you need to when button is pressed then...
self.myButton.text = #"New Button Text";
[self.myButton removeTarget:self action:#selector(myButtonPressedFirstTime) forControlEvents:UIControlEventTouchUpInside];
[self.myButton addTarget:self action:#selector(myButtonPressedAgain) forControlEvents: UIControlEventTouchUpInside];
}
- (IBAction) myButtonWasPressedAgain
{
// this code will run the subsequent times your button is pressed
}