Cocoa Touch - Keyboard Capturing - cocoa-touch

How can I tell what keys the user pressed into a textView?
And before you ask since it sounds similar to a keylogger, I'm making a typing app and I need to know if what they entered of the correct matching key to what they were prompted.
Thanks!

You should set the delegate of the UITextView to one of your classes. (in IB or programmatically, does not matter)
In your delegate, you can put the following function, or something similar:
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
if ( [text length] == 0 ) return YES; // always allow deletion of characters
NSString *new = [textView.text stringByReplacingCharactersInRange:range
withString:text];
if ( [new length] > 100 ) // PUT IN YOUR MAGIC CONDITION HERE
{
return NO; // don't allow the edit to happen
}
return YES; // by default, allow the edit to happen
}
this will only limit input to 100 chars, but you can make it as complicated as you see fit.
edit ps, you asked "what key the user pressed", but since we also have copy&paste and auto-correction, this may give a text which is longer than 1 char!

Related

Parse Issue: Expected Expression (Objective C)

For what ever reason my xcode has decided it doesn't like me... I'm getting the error stated in the title on this line
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSInteger row = [_tableView selectedRow];
if (row == –1) //<---- this line
{
return;
}
NSString *selectedVoice = [_voices objectAtIndex:row];
[_speechSynth setVoice:selectedVoice];
NSLog(#"new voice = %#", selectedVoice);
}
I do believe that it has something to do with _tableView being befuddled because when I attempted to get the IDE to help me to type (you know when it guesses what you might what to finish your word with by doing an API lookup of available functions) it doesn't show selectedRow as a possibility :(
incase it's needed i've put the .m and .h in a pastebin to save some space on your screens... FYI I'm following the Coca Programming for Mac OSX fourth Edition chapter 6.10
In your line
if (row == –1)
the minus-sign is not the real minus-sign, but an "EN DASH" (Unicode U+2013). Perhaps you accidentally pressed the option-key together with the minus-key when typing that code.
Replacing that character with a minus-sign fixes the problem.
UITableView doesn't have a method called selectedRow.
Perhaps you should be using:
- (NSIndexPath *)indexPathForSelectedRow

Detect Enter/Tab/Up/Down keys in NSTextView?

Cocoa noob here. I'm wondering how I can capture the Enter and tab keys onKeyDown whilst the user is typing in an NSTextView?
Thanks!
The easiest way is to implement the - (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector delegate method and look for the insertNewline: and insertTab: selectors.
- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
{
if (aSelector == #selector(insertNewline:)) {
// Handle the Enter key
return YES;
} else if (aSelector == #selector(insertTab:)) {
// Handle the Tab key
return YES;
}
return NO;
}
You should handle keyDown:(NSEvent*)theEvent message of NSTextView (i.e. write your own descendant).
In this event you will have key code in [theEvent keyCode].
For return there is a constant kVK_Return, for tab - kVK_Tab, etc.
You should add Carbon framework (and #import Carbon/Carbon.h) to access these constants.

How do I display a popup informing a NSTextField can't be empty in cocoa ?

How do I display a popup informing a NSTextField can't be empty in cocoa ?
If the user click apply and the NSTextField is empty a popup should appear saying the field can't be empty.
thanks
The answer by #beryllium only tells part of the story.
In fact, to properly validate text field input in Cocoa you should be using an NSFormatter attached to your text field cell and then in your NSTextFieldDelegate you should implement the method: control:didFailToFormatString:errorDescription:. In this delegate method you can prompt the user to correct their input.
All you need to do in your NSFormatter subclass is something like this:
#implementation RKTextFormatter
- (NSString*)stringForObjectValue:(id)object
{
return (NSString*)object;
}
- (BOOL)getObjectValue:(id*)object forString:(NSString*)string errorDescription:(NSString**)error {
BOOL stringPassesTest = NO;
//put your test here
if(!stringPassesTest)
{
//set the error and return NO
if(error)
*error = [NSError errorWithDomain:#"YourErrorDomain" code:1 userInfo:[NSDictionary dictionaryWithObject:#"It's a bingo" forKey:NSLocalizedDescriptionKey]];
return NO;
}
//otherwise, just assign the string
*object = string;
return YES;
}
#end
You would assign the formatter to your text field like so:
RKTextFormatter* formatter = [[RKTextFormatter alloc] init];
[[textField cell] setFormatter:formatter];
And then in your NSTextFieldDelegate you handle any invalid input:
- (BOOL)control:(NSControl *)control didFailToFormatString:(NSString *)string errorDescription:(NSString *)error
{
//display an alert, obviously it would be more useful than this
NSAlert* alert = [NSAlert alertWithMessageText:#"You have failed me for the last time" defaultButton:#"Revise Input" alternateButton:nil otherButton:nil informativeTextWithFormat:#"%#",error];
[alert beginSheetModalForWindow:control.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
//if you return NO here, the user's input will not be accepted
//and the field will remain in focus
return NO;
}
If the user click apply and the NSTextField is empty a popup should
appear saying the field can't be empty.
Please, please, do not do this. You can be smarter than that.
Instead of investing your time in writing an alert dialog to handle the "unexpected" situation, invest it in creating a method that prevents the problem from occurring in the first place: keep the Apply button disabled until a proper value has been entered in the text field.
In addition, as #Rob Keniger mentioned, you should consider using NSFormatters to validate the input to make sure it's of the appropriate kind.
Try to use this code:
- (IBAction)pushBtn:(id)sender {
if(self.textfield.stringValue.length == 0){
NSAlert * alert = [NSAlert alertWithMessageText:#"Error"
defaultButton:#"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:#"Enter a text into text field"];
[alert beginSheetModalForWindow:self.window
modalDelegate:self
didEndSelector:#selector(alertDidHidden:)
contextInfo:nil];
//[alert runModal]; - more simple way
}
}
-(void)alertDidHidden:(NSAlert *)alert{
}

Suppressing the text completion dropdown for an NSTextField

I'm trying to create the effect of an NSComboBox with completes == YES, no button, and numberOfVisibleItems == 0 (for an example, try filling in an Album or Artist in iTunes's Get Info window).
To accomplish this, I'm using an NSTextField control, which autocompletes on -controlTextDidChange: to call -[NSTextField complete:], which triggers the delegate method:
- (NSArray *)control:(NSControl *)control
textView:(NSTextView *)textView
completions:(NSArray *)words
forPartialWordRange:(NSRange)charRange
indexOfSelectedItem:(NSInteger *)index;
I've gotten this working correctly, the only problem being the side effect of a dropdown showing. I would like to suppress it, but I haven't seen a way to do this. I've scoured the documentation, Internet, and Stack Overflow, with no success.
I'd prefer a delegate method, but I'm open to subclassing, if that's the only way. I'm targeting Lion, in case it helps, so solutions don't need to be backward compatible.
To solve this, I had to think outside the box a little. Instead of using the built-in autocomplete mechanism, I built my own. This wasn't as tough as I had originally assumed it would be. My -controlTextDidChange: looks like so:
- (void)controlTextDidChange:(NSNotification *)note {
// Without using the isAutoCompleting flag, a loop would result, and the
// behavior gets unpredictable
if (!isAutoCompleting) {
isAutoCompleting = YES;
// Don't complete on a delete
if (userDeleted) {
userDeleted = NO;
} else {
NSTextField *control = [note object];
NSString *fieldName = [self fieldNameForTag:[control tag]];
NSTextView *textView = [[note userInfo] objectForKey:#"NSFieldEditor"];
NSString *typedText = [[textView.string copy] autorelease];
NSArray *completions = [self comboBoxValuesForField:fieldName
andPrefix:typedText];
if (completions.count >= 1) {
NSString *completion = [completions objectAtIndex:0];
NSRange difference = NSMakeRange(
typedText.length,
completion.length - typedText.length);
textView.string = completion;
[textView setSelectedRange:difference
affinity:NSSelectionAffinityUpstream
stillSelecting:NO];
}
}
isAutoCompleting = NO;
}
}
And then I implemented another delegate method I wasn't previously aware of (the missing piece of the puzzle, so to speak).
- (BOOL)control:(NSControl *)control
textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
// Detect if the user deleted text
if (commandSelector == #selector(deleteBackward:)
|| commandSelector == #selector(deleteForward:)) {
userDeleted = YES;
}
return NO;
}
Update: Simplified and corrected solution
It now doesn't track the last string the user entered, instead detecting when the user deleted. This solves the problem in a direct, rather than roundabout, manner.

NSTable loses focus after present:error

I have an NSTableView that lists tags that are stored using Core Data. The default value for a tag is 'untitled' and I need each tag to be unique, so I have a validation routine that traps empty and non-unique values and that works fine. I don't want the user to be able to store the 'untitled' value for a tag, so I am observing the NSControlTextDidEndEditingNotification, which calls the following code:
- (void)textEndedEditing:(NSNotification *)note {
NSString *enteredName = [[[note userInfo] valueForKey:#"NSFieldEditor"] string];
if ([enteredName isEqualToString:defaultTagName]) {
NSString *dString = [NSString stringWithFormat:#"Rejected - Name cannot be default value of '%#'", defaultTagName];
NSString *errDescription = NSLocalizedStringFromTable( dString, #"Tag", #"validation: default name error");
NSString *errRecoverySuggestion = NSLocalizedStringFromTable(#"Make sure you enter a unique value for the new tag.", #"Tag", #"validation: default name error suggestion");
int errCode = TAG_NAME_DEFAULT_VALUE_ERROR_CODE;
NSArray *objArray = [NSArray arrayWithObjects:errDescription, errRecoverySuggestion, nil];
NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedRecoverySuggestionErrorKey, nil];
NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
NSError *error = [[NSError alloc] initWithDomain:TAG_ERROR_DOMAIN code:errCode userInfo:eDict];
NSBeep();
[preferencesWindowsController presentError:error];
unsigned long index = [self rowWithDefaultTag];
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
// [self editColumn:0 row:index withEvent:nil select:YES];
}
}
- (unsigned long)rowWithDefaultTag {
__block unsigned long returnInt;
[managedTags enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([[obj valueForKey:#"name"] isEqualToString:defaultTagName]) {
returnInt = idx;
*stop = YES;
}
}];
return returnInt;
}
With the 'editColumn' line commented out, the code works, so if the user accepts the default tag name without editing it, the error is built, displayed and the process finishes by leaving the appropriate row in the table highlighted.
However, I would like to take it that step further and place the user in edit mode. When I uncomment the 'editColumn' line, the behaviour is not at all what I expected - the tableView loses its blue focus box and the row that respresents the new tag is blank. If I click on the tableView, the row becomes visible. I've spent a lot of time on this and have got nowhere, so some help with this would be very much appreciated.
(Note: I tried using textDidEndEditing, which also didn't behave as I expected, but that is a separate issue!)
Answering my own question. Doh!
I already had a method which I used to put the user in edit mode when they clicked the button to add a new tag:
- (void)objectAdded:(NSNotification *)note {
if ([[note object] isEqual:self]) {
[self editColumn:0 row:[self rowWithDefaultTag] withEvent:nil select:YES];
}
}
Creating a notification to call this solves the problem and places the user in edit mode correctly. The important thing is not to try to do this on the existing runloop; so sending the notification as follows postpones delivery until a later runloop:
// OBJECTADDED is a previously defined constant.
NSNotification * note = [NSNotification notificationWithName:OBJECTADDED object:self];
[[NSNotificationQueue defaultQueue] enqueueNotification: note postingStyle: NSPostWhenIdle];
Problem solved. I wasted a lot of time trying to solve this - a classic example of getting too involved in the code and not looking at what I'm trying to do.
I've forgotten where I first saw this posted - whoever you are, thank you!