Can't set BecomeFirstResponder in EditingDidEnd Action - cocoa-touch

I am trying to do Error Checking for a Correct Time in a UItextField. I didn't want an error to pop up while the user is typing (To give them the chance to fix it first) so I thought I would do the Check during an EditingDidEnd Action. I setup ones for Hours, Minutes and Seconds. Here is the code I tried for inHours:
- (IBAction)inHourEditEnd:(id)sender
{
// Check to make sure value is between 0 & 23 Hours
if ([[[self inHour] text] intValue] >= 0 && [[[self inHour] text] intValue] < 24) {
[self updateDuration];
} else {
NSString *errorDescription = [NSString stringWithFormat:#"%# hours is not a valid start time. Please enter an hour between 0 and 23", [[self inHour] text]];
UIAlertView *errorMessage = [[UIAlertView alloc] initWithTitle:#"Ivalid Hour for Start Time"
message:errorDescription
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[errorMessage show];
[_inHour becomeFirstResponder];
}
}
The alert works fine but it won't go back to the textfield (inHour) after showing the alert.
It just stays on whatever textField I tapped to cause EditingDidEnd. Searching here I found a way to make the alertView send the user back to the right textbox using this code:
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
//Checks For Approval
if (buttonIndex == 0) {
[_inHour becomeFirstResponder];
}
}
But this will only work for the first box and I need to make it work with inHour, inMinute and inSeconds.
Any Suggestions how I can make this work?
Is either one of these paths in the right direction?
Thank you for any help.

If you really want to do your validation after the text field has already lost first responder status, you could add a property to your view controller to track the text field that needs to receive becomeFirstResponder:
// in your .m file, declare a class extension for "private" members
#interface MyViewController ()
#property (weak, nonatomic) UITextField *lastValidationFailureField;
#end
// add this to inHourEditEnd:
self.lastValidationFailureField = _inHour;
// use this property in alertView:clickedButtonAtIndex:
[self.lastValidationFailureField becomeFirstResponder];
self.lastValidationFailureField = nil;
Alternatively, you could prevent the user from leaving the text field. Have your view controller implement UITextFieldDelegate and set all of your text fields’ delegate outlets to your view controller. Then implement textFieldShouldEndEditing:
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
if (textField == [self inHour]) {
if ([[textField text] intValue] >= 0 && [[textField text] intValue] < 24) {
[self updateDuration];
} else {
NSString *errorDescription = ...
UIAlertView *errorMessage = ...
[errorMessage show];
// prevent the text field from even losing first responder status
return NO;
}
}
if (textField == [some someOtherTextFieldToValidate)] {
...
}
return YES;
}
I put all of the logic in a single method here for brevity, I would recommend at least some minimal refactoring of that code if you have more than a couple of text fields to validate.
You would not need your actions method such as inHourEditEnd: any more.

Related

Unexpected interface name 'name':expected expression message

I'm at a stage in my coffee order app where I have a UIAlertView pop up from a DetailViewController asking a yes/no to proceed. The 'yes' goes to the next View Controller, which is fine, but the 'no' also goes to the same view controller, but I just want it to return to the DetailViewController, to give the user the option to change their order. I'm using if statements as you can see in the code, but the following code comes up with the "Unexpected interface name 'DetailViewController':expected expression message"
I am new to this, a student learning, and while there are tutorials out there, this one piece of code in my second 'if' statement for buttonIndex ==1 should be correct according to a few solutions on this site, but for some reason, I'm missing something here. I'm using XCode v7.0.1, and am also having difficulty finding up to date solutions for this version.
(note I am aware of the deprecated code for the UIAlertView, but it is working)
Here is the code, appreciate some help:
#import "DetailViewController.h"
#interface DetailViewController ()
#end
#implementation DetailViewController
#synthesize YourOrder;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.DetailTitle.text = _DetailModule[0];
self.DetailDescription.text = _DetailModule[1];
self.DetailImageView.image = [UIImage imageNamed:_DetailModule[2]];
self.navigationItem.title = _DetailModule[0];
self.YourOrder.text = #"";
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)addItem:(id)sender {
self.YourOrder.text = [NSString stringWithFormat:#"%# \n %#",
self.YourOrder.text, [sender currentTitle]];
}
- (IBAction)alertView:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Send Order" message:#"Are you happy with your order?" delegate:self cancelButtonTitle:#"Yes" otherButtonTitles:#"No",nil];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// the user clicked Yes
if (buttonIndex == 0)
{
// do something here...
}
// the user clicked No - need to write code to return a 'no' click to the DetailViewController to try again.
if (buttonIndex == 1)
{
[self presentViewController:DetailViewController animated:YES completion:nil];
}
}
#end
I have also tried:
if (buttonIndex == 1)
{
DetailViewController *detailView = [[DetailViewController alloc] init];
[self presentViewController:detailView animated:YES completion:nil];
}
but it still goes to the same View controller and I see the "Warning: Attempt to present on whose view is not in the window hierarchy!" error message.

Force NSTextField to send textDidEndEditing

I have a subclass of NSTextField that I made so that when a user is done editing the field, the text field will lose focus. I also have it set up so whenever the user clicks on the main view, this will act as losing focus on the textfield. And this all works great. Now I want to add some additional capabilities to the subclass.
I want the textfield to send a textDidEndEditing every time a user clicks anywhere outside of the box. This includes when a user clicks on another UI component. The behavior I'm seeing right now is that when a user clicks on another UI component (let's say a combo box) the action does not trigger. Is there a way to force this? Besides manually adding it as a part of the other components actions?
Any help would be appreciated!
Here's the code for my textDidEndEditing function
- (void)textDidEndEditing:(NSNotification *)notification
{
NSString *file = nil;
char c = ' ';
int index = 0;
[super textDidEndEditing:notification];
if ([self isEditable])
{
// is there a valid string to display?
file = [self stringValue];
if ([file length] > 0)
{
c = [file characterAtIndex:([file length] - 1)];
if (c == '\n') // check for white space at the end
{
// whitespace at the end... remove
NSMutableString *newfile = [[NSMutableString alloc] init];
c = [file characterAtIndex:index++];
do
{
[newfile appendFormat:#"%c", c];
c = [file characterAtIndex:index++];
}
while ((c != '\n') && (index < [file length]));
[self setStringValue:newfile];
file = newfile;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:#"inputFileEntered" object:self];
}
}
// since we're leaving this box, show no text in this box as selected.
// and deselect this box as the first responder
[self setSelectedText:0];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"setResponderToNil" object:self];
}
Where "setSelectedText" is a public function in the text field subclass:
- (void)setSelectedText:(int) length
{
int start = 0;
NSText *editor = [self.window fieldEditor:YES forObject:self];
NSRange range = {start, length};
[editor setSelectedRange:range];
}
And the "setResponderToNil" notification is a part of my NSView subclass:
- (void)setResponderToNil
{
AppDelegate *delegate = (AppDelegate *)[NSApp delegate];
[delegate.window makeFirstResponder:nil];
}
I think I found a way to do this. It may not be the most eloquent, but it seems to work with the type of behavior I want.
I added an mouse event listener to the app's main controller:
event_monitor_mousedown_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSRightMouseDown
handler:^NSEvent *(NSEvent * event)
{
NSResponder *resp = [[[NSApplication sharedApplication] keyWindow] firstResponder];
if ([resp isKindOfClass:[NSTextView class]])
{
// set UI in proper state - remove focus from text field
// even when touching a new window for the first time
[[NSNotificationCenter defaultCenter]
postNotificationName:#"setResponderToNil" object:self];
[self setStopState];
}
return event;
}];
This event checks the current responder in the application on any mouseDown action. If it's a textView object (which is type of the object that would be the first responder when editing an NSTextField) it will send the notification to set the firstResponder to nil. This forces the textDidEndEditing notification. I want to play around with it some more to see if I'm getting the right expected behavior. I hope this helps someone out there!

confirmation before open in safari

Im trying to add a UIAlertView to warn the user that the link will open in Safari. The user can then choose OK(open the url) or cancel which should just close the alert and return to the links.
I have three different UIButtons which has 3 different URLs.
Right now ive added a IBAction to all buttons and all buttons has Tags (which i think i can use somehow :D). I guess - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex: delegate will be good to use to..
My question: how should the UIAlertView know what URL to open i user clicks ok?
I suggest that you use a subclass of UIAlertView that is able to track some more properties along. I do this in all my projects and it is much simpler.
One solution to do this would be to subclass UIAlertView to MyAlertView and add a #property(nonatomic, retain) id userInfo; or #property(nonatomic, retain) NSURL* urlToOpen. Thus you can attach custom data to your UIAlertView and retrieve it in the delegate method to do whatever you need with it.
Another solution, and my preferred one actually, is to add Objective-C blocks support to UIAlertView, to be able to use UIAlertView using the blocks API instead of using a delegate. This is particularly useful if you use multiple UIAlertViews in the same class and with the same delegate, as using a single delegate to handle the different instances is a mess.
I personally use this technique all the time, as it also makes my code more readable by having the code that executes when the button is tapped right next to the code that shows the alert, instead of having it at a complete different places when you use delegate methods.
You can look at my OHAlertView subclass here on GitHub that implement this already. The usage is really simple and allow you to use blocks for each alert view instead of a common delegate, see below.
Usage Example
-(void)confirmOpenURL:(NSURL*)url
{
NSString* message = [NSString string WithFormat:#"Open %# in Safari?",
url.absoluteString];
[OHAlertView showAlertWithTitle:#"Open URL"
message:message
cancelButton:#"No"
okButton:#"Yes"
onButtonTapped:^(OHAlertView* alert, NSInteger buttonIndex)
{
if (buttonIndex != alert.cancelButtonIndex)
{
// If user tapped "Yes" and not "No"
[[UIApplication sharedApplication] openURL:url];
}
}];
}
Then each button can have its own action:
-(IBAction)button1Action
{
[self confirmOpenURL:[NSURL URLWithString:#"http://www.google.com"]];
}
-(IBAction)button2Action
{
[self confirmOpenURL:[NSURL URLWithString:#"http://www.stackoverflow.com"]];
}
Or you can have a common IBAction for all your buttons opening URLs:
-(IBAction)commonButtonAction:(UIButton*)sender
{
NSUInteger tag = sender.tag;
NSString* urls[] = { #"http://www.google.com", #"http://www.stackoverflow.com" };
NSURL* buttonURL = [NSURL URLWithString: urls[tag] ]; // in practice you should check that tag is between 0 and the number of urls to be sure, that's just an example here
[self confirmOpenURL:buttonURL];
}
Solved it like this:
added a tag when creating the UIAlertView. Like this:
- (IBAction)PressedButton {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Link"
message:#"Want to open in safari?"
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
message.tag = 2; //different tag for each button
[message addButtonWithTitle:#"Cancel"];
[message show];
}
Then when - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex delegate was thrown I did this:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == alertView.cancelButtonIndex){
if (alertView.tag == 1)
{
//go to URL1
}
else if (alertView.tag == 2)
{
//go to URL2
}
else if (alertView.tag == 3)
{
//go to URL3
}
}
}
Your button action method should have a signature like this:
-(void)doSomething:(id)sender;
whereby sender will be the button. Based on this you could find out which URL is meant.

open a uiviewcontroller from uialertview button

I want to open a UIViewController if a UIALertView Button is pressed.
I have the code for that. However, the uiviewcontroller is not being opened :(
I am sure the uialertview is working fine and all. and the code for the uiviewcontroller is fine as well. (it works when called from other places).
Any help ?
Thanks.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) //Yes, Sign Me Up!
{
NSLog(#"0");
ViewerWebController *web = [[ViewerWebController alloc] initWithURL:[NSURL URLWithString:#"http://www.funimation.com/"]];
[web setShowToolbar:YES];
[self.navigationController pushViewController:web animated:YES];
[web release];
}
else if (buttonIndex == 1) //Remove from List
{
NSLog(#"1");
[[NSUserDefaults standardUserDefaults] setBool:false forKey:#"subscribeButtonOption"];
[_tableView reloadData];
}
else if (buttonIndex == 2) //"Maybe Later"
{
NSLog(#"2");
}
}
The alert view doesn't have a navigation controller. You would need to keep a reference to the navigation controller you want to push the view controller on to.
This may work..
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
// the user clicked one of the OK/Cancel buttons
if (buttonIndex == 0)
{
YourRootNavigationController *navController = [YourRootNavigationController sharedInstance]; // singleton
[navController pushViewController:YOUR_CONTROLLER animated:YES];
}
else
{
NSLog(#"cancel");
}
}
Use didDismissWithButtonIndex rather clickedButtonAtIndex.
The former is invoked after the UIAlertView has been removed from the screen hierarchy, while the latter occurs while the UIAlertView is still on screen. While on screen, UIAlertView alters the nature of the app; that means bad things happen when trying to push views to the nav controller.
This misuse of clickedButtonAtIndex seems to be common misinformation floating around the internet and StackOverflow. Cost me hours of frustration.

disable button when textfields are empty

hi im a beginner to programming and have been stuck on this task for ages and seem to be getting nowhere.
basically i have several textfields that generates the input information on a different page when the user presses a button. i would like the button to be disabled until all text fields are filled with information.
so far i have this:
- (void)textFieldDidEndEditing:(UITextField *)textField
{
// make sure all fields are have something in them
if ((textfbookauthor.text.length > 0)&& (textfbookedition.text.length > 0)&& (textfbookplace.text.length > 0)&& (textfbookpublisher.text.length > 0) && (textfbookpublisher.text.length > 0) && (textfbooktitle.text.length > 0) && (textfbookyear.text.length > 0)) {
self.submitButton.enabled = YES;
}
else {
self.submitButton.enabled = NO;
}
}
the problem is the 'submitButton' is coming up with an error, what needs to go in its place?
i tried to put my button 'bookbutton'in it instead but its not working.
this is my function for the 'generate' button
-(IBAction)bookbutton:(id)sender;
{
NSString* combinedString = [NSString stringWithFormat:
#"%# %# %#.%#.%#:%#.",
textfbookauthor.text,
textfbookyear.text,
textfbooktitle.text,
textfbookedition.text,
textfbookplace.text,
textfbookpublisher.text];
BookGenerate*bookg = [[BookGenerate alloc] init];
bookg.message = combinedString;
bookg.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:bookg animated:YES];
[BookGenerate release];
}
if anybody knows how i can make it work or what i need to add please help.
thanks in advance
Make an Outlet for every UITextField and create an IBAction in your .h:
IBOutlet UITextField *textField1;
IBOutlet UITextField *textField2;
IBOutlet UITextField *textField3;
IBOutlet UIButton *button
- (IBAction)editingChanged;
Connect all the outlets and connect the IBAction to every textfield with editingChanged:
- (IBAction)editingChanged {
if ([textfield1.text length] != 0 && [textfield2.text length] != 0 && [textfield3.text length] != 0) {
[button setEnabled:YES];
}
else {
[button setEnabled:NO];
}
}
Note that you can also use [textfield.text isEqualToString:#""] and put a ! in front of it (!means 'not') to recognize the empty textField, and say 'if the textField is empty do...'
And:
- (void)viewDidLoad {
[super viewDidLoad];
[button setEnabled:NO];
}
If you have a button and you want to enable it only if there is a text in a textfield or textview, implement the follow method
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if (textView.text.length > 1 || (text.length > 0 && ![text isEqualToString:#""]))
{
self.button.enabled = YES;
}
else
{
self.button.enabled = NO;
}
return YES;
}
Few solutions are available on these posts as well
Very Similar Query
A different version of it
Key here is using (extending) UITextFieldDelegate class and it's associated function
//Need to have the ViewController extend UITextFieldDelegate for using this feature
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Find out what the text field will be after adding the current edit
let text = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
if !text.isEmpty{//Checking if the input field is not empty
button.userInteractionEnabled = true //Enabling the button
} else {
button.userInteractionEnabled = false //Disabling the button
}
// Return true so the text field will be changed
return true
}
You should use" textfield should change chracters in range" (see apple docs for uitextviewdelegate). because it updates as users type information." Textfield did end editing" only fires when the users finishes editing a textfield or hits enter. In most cases, someone will fill in the last field and leave the cursor sitting there and expect the button to become enabled...but that wont happen using "textfield did end editing" just because they entered input.
You can also do this to check the length
if([self.textfiele.text isEqualToString:#""])
In addition to having IBOutlets for each UITextField, it might be helpful to have an IBOutletCollection declared that contains all 3 text fields in an array.
#property (nonatomic, retain) IBOutletCollection(UITextField) NSArray *textFields;
Then in your implementation just #synthesize textFields and you can quickly loop the text fields it contains, checking for text.
- (IBAction)editingChanged
{
BOOL buttonShouldBeEnabled = YES;
for (UITextField *field in self.textFields)
if (!field.text.length)
buttonShouldBeEnabled = NO;
button.enabled = buttonShouldBeEnabled;
}