I have a calls Called Modal that I am running the following code in.
- (void)createAccessoryView
{
CGRect frame = CGRectMake(0.0, self.frame.size.height, self.frame.size.width, 44.0);
fieldAccessoryView = [[UIToolbar alloc] initWithFrame:frame];
fieldAccessoryView.barStyle = UIBarStyleBlackOpaque;
fieldAccessoryView.tag = 200;
[fieldAccessoryView setBarStyle:UIBarStyleBlack];
UIBarButtonItem *spaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done:)];
UISegmentedControl* segmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:NSLocalizedString(#"Previous", #""), NSLocalizedString(#"Next", #""), nil]];
[segmentedControl addTarget:self action:#selector(segmentAction:) forControlEvents:UIControlEventValueChanged];
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
[segmentedControl setMomentary:YES];
UIBarButtonItem *segmentButton = [[UIBarButtonItem alloc] initWithCustomView:segmentedControl];
[fieldAccessoryView setItems:[NSArray arrayWithObjects:segmentButton, spaceButton, doneButton, nil] animated:NO];
}
-(void)segmentAction:(id)selector
{
}
I then create a class that extends Modal and has a few UITextFields. Clicking on a text field brings up the keyboard as expected. Once the keyboard is launched I see the previous / next and done buttons. Clicking done throws and error and doesn't use the segmentAction method as it should. Not really sure why.
here is the stack trace I get after clicking the done button
2013-03-13 15:54:33.956 myapp[74194:c07] -[NotesModal done:]: unrecognized selector sent to instance 0x80f3fb0
2013-03-13 15:54:33.961 myapp[74194:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NotesModal done:]: unrecognized selector sent to instance 0x80f3fb0'
*** First throw call stack:
(0x1b54012 0x1470e7e 0x1bdf4bd 0x1b43bbc 0x1b4394e 0x1484705 0x3b82c0 0x5f4a64 0x1484705 0x3b82c0 0x3b8258 0x479021 0x47957f 0x4786e8 0x3e7cef 0x3e7f02 0x3c5d4a 0x3b7698 0x1aafdf9 0x1ad7f3f 0x1ad796f 0x1afa734 0x1af9f44 0x1af9e1b 0x1aae7e3 0x1aae668 0x3b4ffc 0x1786d 0x24b5)
libc++abi.dylib: terminate called throwing an exception
The important part of the error is this:
[NotesModal done:]: unrecognized selector
Hence, it's crashing because it doesn't recognize the method done:.
Make sure that you actually have a done: method, such as this:
-(void)done:(id)sender
{
// whatever it does here...
}
Note -(void)done and -(void)done:(id)sender are not the same.
this code
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:#selector(done:)];
needs a method
-(void)done:(id)selector
{
//…
}
either provide it or change the UIBarButtonItem to
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:#selector(segmentAction:)];
Please check if your done method has any parameter.
It needs to be something like:
-(void)done:(id)selector
{
[self dismissModalViewController];
}
Related
I have seven UIBarButtonItems in a UIToolbar.
UIBarButtonSystemItemFixedSpace
UIBarButtonSystemItemCancel
UIBarButtonSystemItemFlexibleSpace
UIBarButton with custom text
UIBarButtonSystemItemFlexibleSpace
UIBarButtonSystemItemSave
UIBarButtonSystemItemFixedSpace
Everything is laying out as I would normally expect it to except for the UIBarButtonSystemItemSave item. It has an extra space next to it.
I added borders to each subview inside of the toolbar to try to figure out what was happening but still couldn't quite figure it out.
Maybe there was something happening with the order of the buttons so I switched the items around in order, but still couldn't figure it out.
Just in case it was something about the way I created the button, I changed the init option to UIBarButtonSystemItemCancel for both Save and Cancel.
Here's the code that I've been using.
- (UIToolbar *)headerToolbar {
if (!_headerToolbar) {
_headerToolbar = [[UIToolbar alloc] initWithFrame:self.headerToolbarFrame];
_headerToolbar.barTintColor = [[OVThemeManager shared] blueColor];
if (UIApplication.sharedApplication.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight) {
_headerToolbar.items = #[[self fixedSpace:buttonBufferWidth],
self.cancelButton,
self.flexibleSpace,
self.titleButton,
self.flexibleSpace,
self.saveButton,
[self fixedSpace:buttonBufferWidth]];
} else {
_headerToolbar.items = #[[self fixedSpace:buttonBufferWidth],
self.saveButton,
self.flexibleSpace,
self.titleButton,
self.flexibleSpace,
self.cancelButton,
[self fixedSpace:buttonBufferWidth]];
}
[self borderSubviews:self.headerToolbar];
}
return _headerToolbar;
}
- (void)borderSubviews:(UIView *)superview {
superview.layer.borderWidth = 1.0f;
superview.layer.borderColor = [UIColor colorWithRed:0.001f *(float)(arc4random()%1000)
green:0.001f *(float)(arc4random()%1000)
blue:0.001f *(float)(arc4random()%1000)
alpha:1.0f].CGColor;
for (UIView *subview in superview.subviews) {
[self borderSubviews:subview];
}
}
- (CGRect)headerToolbarFrame {
CGRect frame = self.view.bounds;
frame.size.height = headerToolbarHeight;
return frame;
}
- (UIBarButtonItem *)saveButton {
if (!_saveButton) {
_saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(saveButtonTouched:)];
_saveButton.tintColor = [[OVThemeManager shared] blueColor];
}
return _saveButton;
}
- (UIBarButtonItem *)cancelButton {
if (!_cancelButton) {
_cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(cancelButtonTouched:)];
_cancelButton.tintColor = [[OVThemeManager shared] blueColor];
}
return _cancelButton;
}
- (UIBarButtonItem *)titleButton {
if (!_titleButton) {
_titleButton = [[UIBarButtonItem alloc] initWithTitle:#"Other button".localized
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_titleButton.tintColor = [[OVThemeManager shared] blueColor];
}
return _titleButton;
}
- (UIBarButtonItem *)flexibleSpace {
return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];
}
- (UIBarButtonItem *)fixedSpace:(CGFloat)width {
UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil
action:nil];
fixedSpace.width = width;
return fixedSpace;
}
I have a rather confusing problem: In my application I am checking for network connectivity and if the user is not connected to the internet I am showing a little red exclamation mark in the navigation bar.
The Code that is used to show the exclamation mark looks like this:
- (void)showConnectivityMessage
{
UIBarButtonItem *exclamationMark = [[UIBarButtonItem alloc]initWithTitle:#"!" style:UIBarButtonItemStyleDone target:self action:#selector(showConnectivityInfo)];
[exclamationMark setTitleTextAttributes:#{NSForegroundColorAttributeName:[UIColor redColor],
NSFontAttributeName:[UIFont boldSystemFontOfSize:22]} forState:UIControlStateNormal];
NSMutableArray *items = [NSMutableArray arrayWithArray:self.navigationItem.leftBarButtonItems];
[items addObject:exclamationMark];
[self.navigationItem setLeftBarButtonItems:items];
NSLog(#"Should show exclamation mark!");
}
The method is called correctly and I can see the output "Should show exclamation mark!". Now, whats weird is that it takes another ~20s until the new navigation item actually appears...
Does anyone have an idea on where this delay could be coming from? Any help resolving the error would be greatly appreciated!
Every time you see a similar delay in UI, it is because you haven't updated the UI from the main thread. Always update UI only from the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
UIBarButtonItem *exclamationMark = [[UIBarButtonItem alloc]initWithTitle:#"!" style:UIBarButtonItemStyleDone target:self action:#selector(showConnectivityInfo)];
[exclamationMark setTitleTextAttributes:#{NSForegroundColorAttributeName:[UIColor redColor],
NSFontAttributeName:[UIFont boldSystemFontOfSize:22]} forState:UIControlStateNormal];
NSMutableArray *items = [NSMutableArray arrayWithArray:self.navigationItem.leftBarButtonItems];
[items addObject:exclamationMark];
[self.navigationItem setLeftBarButtonItems:items];
NSLog(#"Should show exclamation mark!");
}
there is some code that is blocking the main thread. Try this. It may help you
- (void)showConnectivityMessage
{
dispatch_async(dispatch_get_main_queue(), ^{
UIBarButtonItem *exclamationMark = [[UIBarButtonItem alloc]initWithTitle:#"!" style:UIBarButtonItemStyleDone target:self action:#selector(showConnectivityInfo)];
[exclamationMark setTitleTextAttributes:#{NSForegroundColorAttributeName:[UIColor redColor],
NSFontAttributeName:[UIFont boldSystemFontOfSize:22]} forState:UIControlStateNormal];
NSMutableArray *items = [NSMutableArray arrayWithArray:self.navigationItem.leftBarButtonItems];
[items addObject:exclamationMark];
[self.navigationItem setLeftBarButtonItems:items];
NSLog(#"Should show exclamation mark!");
});
}
I am creating a picker view that is shown when the user taps on a text field.
It works fine. But I want to dismiss the picker view when the user taps on the custom Done button. This is my code so far:
- (void)showPickerWithDoneButton:(UITextField *)sender
{
UITextField *textField = sender;
// Creamos UIPickerView como una vista personalizada de un keyboard View
UIPickerView *pickerView = [[UIPickerView alloc] init];
[pickerView sizeToFit];
pickerView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
pickerView.delegate = self;
pickerView.dataSource = self;
pickerView.showsSelectionIndicator = YES;
//UIPickerView
//Asignamos el pickerview al inputView de nuestro texfield
self.tipos_auto.inputView = pickerView;
// Preparamos el botón
UIToolbar* keyboardDoneButtonView = [[UIToolbar alloc] init];
keyboardDoneButtonView.barStyle = UIBarStyleDefault;
keyboardDoneButtonView.translucent = YES;
keyboardDoneButtonView.tintColor = nil;
[keyboardDoneButtonView sizeToFit];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle: NSLocalizedString(#"Aceptar", #"Button") style:UIBarButtonItemStyleBordered target:self action:#selector(pickerHechoClicked:)];
doneButton.tintColor = [UIColor blackColor];
//Para ponerlo a la derecha del todo voy a crear un botón de tipo Fixed Space
UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
fixedSpace.width = keyboardDoneButtonView.frame.size.width - 150;
[keyboardDoneButtonView setItems:[NSArray arrayWithObjects:fixedSpace, doneButton, nil]];
// Finalmente colocamos la keyboardDoneButtonView en el text field...
textField.inputAccessoryView = keyboardDoneButtonView;
}
And this is the method that should dismiss the picker view:
-(void) pickerHechoClicked :(id)sender{
[sender resignFirstResponder];
}
But after tapping on the button the app crashes with following error:
** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIBarButtonItem resignFirstResponder]: unrecognized selector sent to instance
Any help is welcome.
The easiest thing to do is to ask the view to finalise all editing operations, because then it doesn't matter what the current first responder actually is (so long as it's a subview somewhere):
[self.view endEditing:YES];
It looks like you are passing the UIBarButtonItem the "pickerHechoClicked" method instead of sending your instance of UIPickerView.
When the done button is pressed you should pass the pickerView variable as the parameter to "pickerHechoClicked" instead.
I do not see the code where you actually assign your custom done button an action but in the code to handle the button press use this:
[self pickerHechoClicked:pickerView];
You cannot call resignFirstResponder on your UIBarbuttonItem but to your picker. So the simple solution is to keep a reference of your picker then call [self.myPicker resignFirstResponder]
I've created a UIToolBar as an inputAccessoryView with a next and previous buttons that will cycle through the textFields in my view controller.
I've created a category on UITextField based on the third SO answer on this page which adds a property to the textField that points to the next/previous textField.
I can get it to cycle through the textFields both forward and backward, but only once, and then my buttons are permanently disabled. Also, when the last textField is in focus, I still need to tap the next button one extra time (4 taps for 3 textFields) to have it disable the next button — same with the previous button, I have to tap back once when I'm in the first textField.
// ViewController.h
#interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, UIPopoverControllerDelegate> {
__weak IBOutlet UITextField *valueField;
__weak IBOutlet UITextField *nameField;
__weak IBOutlet UITextField *serialNumberField;
}
#property (nonatomic, strong) UITextField *currentTextField;
- (IBAction)nextTextField:(id)sender;
- (IBAction)prevTextField:(id)sender;
// ViewController.m
- (void)viewDidLoad {
//...
nameField.delegate = self;
nameField.nextTextField = serialNumberField;
nameField.prevTextField = nil;
serialNumberField.delegate = self;
serialNumberField.nextTextField = valueField;
serialNumberField.prevTextField = nameField;
valueField.delegate = self;
valueField.prevTextField = serialNumberField;
valueField.nextTextField = nil;
//...
}
- (void)viewWillAppear:(BOOL)animated {
//UIToolBar for inputAccessoryView
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
UIBarButtonItem *nextField = [[UIBarButtonItem alloc] initWithTitle:#"\U00003009"
style:UIBarButtonItemStylePlain
target:self
action:#selector(nextTextField:)];
UIBarButtonItem *prevField = [[UIBarButtonItem alloc] initWithTitle:#"\U00003008"
style:UIBarButtonItemStylePlain
target:self
action:#selector(prevTextField:)];
UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(backgroundTapped:)];
NSArray *toolBarButtons = #[prevField, nextField, space, done];
toolBar.items = toolBarButtons;
nameField.inputAccessoryView = toolBar;
valueField.inputAccessoryView = toolBar;
serialNumberField.inputAccessoryView = toolBar;
}
- (IBAction)nextTextField:(id)sender {
UITextField *next = self.currentTextField.nextTextField;
if (!next) {
[sender setEnabled:NO];
} else {
[sender setEnabled:YES];
[next becomeFirstResponder];
}
}
- (IBAction)prevTextField:(id)sender {
UITextField *prev = self.currentTextField.prevTextField;
if (!prev) {
[sender setEnabled:NO];
} else {
[sender setEnabled:YES];
[prev becomeFirstResponder];
}
}
I think part of the problem is that you are handling the enabling/disabling of the bar buttons in a method that is only called once one of the bar buttons has been tapped. It would be better to set the barbuttonitems as properties of your view controller (so you can enable/disable them when you want to), and then handle the enabling/disabling of the bar button items within the UITextField's 'textFieldShouldBeginEditing' delegate method.
So, something like this:
- (void)viewWillAppear:(BOOL)animated {
//UIToolBar for inputAccessoryView
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
self.moveToNextFieldButton = [[UIBarButtonItem alloc] initWithTitle:#"\U00003009"
style:UIBarButtonItemStylePlain
target:self
action:#selector(nextTextField:)];
self.moveToPrevFieldButton = [[UIBarButtonItem alloc] initWithTitle:#"\U00003008"
style:UIBarButtonItemStylePlain
target:self
action:#selector(prevTextField:)];
UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(backgroundTapped:)];
NSArray *toolBarButtons = #[self.moveToPrevFieldButton, self.moveToNextFieldButton, space, done];
toolBar.items = toolBarButtons;
nameField.inputAccessoryView = toolBar;
valueField.inputAccessoryView = toolBar;
serialNumberField.inputAccessoryView = toolBar;
}
-(BOOL)textFieldShouldBeginEditing:(UITextField*)textField{
UITextField *next = textField.nextTextField;
UITextField *prev = textField.prevTextField;
self.moveToNextFieldButton.enabled = next != nil;
self.moveToPrevFieldButton.enabled = prev != nil;
return YES;
}
- (IBAction)nextTextField:(id)sender {
UITextField *next = self.currentTextField.nextTextField;
if (next) {
[next becomeFirstResponder];
}
}
- (IBAction)prevTextField:(id)sender {
UITextField *prev = self.currentTextField.prevTextField;
if (prev) {
[prev becomeFirstResponder];
}
}
I have a UISegmentedControl that I want to appear in an UIToolbar. It appears, but clicking it does not call the method that it should. Am I missing anything?
Code:
-(void)setupToolbars{
NSArray *segItemsArray = [NSArray arrayWithObjects: #"List", #"Day", #"Month", nil];
segmentedControl = [[UISegmentedControl alloc] initWithItems:segItemsArray];
segmentedControl.selectedSegmentIndex = 2;
[segmentedControl addTarget:self action:#selector(changeView) forControlEvents:UIControlEventTouchUpInside];//this line should make the segmented control call the correct method
UIBarButtonItem *segmentedControlButtonItem = [[UIBarButtonItem alloc] initWithCustomView:(UIView *)segmentedControl];
NSArray *barArray = [NSArray arrayWithObjects: todayButtonItem,flexibleSpace, segmentedControlButtonItem, flexibleSpace, nil];
[bottomToolbar setItems:barArray];
}
-(void)changeView{
NSLog(#"changeView");
...
}
You want to use the UIControlEventValueChanged event, not the UIControlEventTouchUpInside event.
Just building on top of what rmaddy stated. I would also suggest use of UIControlEventValueChanged event.
[segmentedControl addTarget:self action:#selector(didChangeSegmentControl:) forControlEvents:UIControlEventValueChanged];
-(void)didChangeSegmentControl:(UISegmentedControl*) control
{
if (control.selectedSegmentIndex ==0 )
{
//...
}
}