Detect Enter/Tab/Up/Down keys in NSTextView? - objective-c

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.

Related

Realtime coredata autosave

I have a little statusbar app. It saves when I close the window, it saves when I quit the app but it isn't going to save every time I edit a row in my tableview or I add something to my arraycontroller.
Is there a method to call saveAction at least every "enter" hit or when I confirm an edit?
A save button is not what I'm searching for.
Thanks in advance.
This is my approach:
1) Create a subclass of NSManagedObject to add functionality for autosaving. You can substitute the line do { try managedObjectContext?.save() } catch { print(error) } by something like saveContext() if you have a global function defined elsewhere. Note that autosave is disabled by default.
class AutoSaveManagedObject: NSManagedObject {
class var autosave: Bool { return false }
var autosave: Bool?
private var previousValue: AnyObject?
override func willChangeValueForKey(key: String) {
super.willChangeValueForKey(key)
if ( autosave == true ) || ( autosave == nil && self.dynamicType.autosave ) {
previousValue = valueForKey(key)
}
}
override func didChangeValueForKey(key: String) {
super.didChangeValueForKey(key)
if ( autosave == true ) || ( autosave == nil && self.dynamicType.autosave ) {
if "\(previousValue)" != "\(valueForKey(key))" {
do { try managedObjectContext?.save() } catch { print(error) }
}
previousValue = nil
}
}
}
2) Make all core data objects subclasses of AutoSaveManagedObject rather than of NSManagedObject. If you want to enable autosaving, you should write something like this:
class MyMO: AutoSaveManagedObject {
override class var autosave: Bool { return true }
// Your #NSManaged vars here
}
3) Now all the instances of MyMO have autosave enabled. If you want to disable it for a certain instance, you can always write:
let myMO = ... as? MyMO
myMO?.autosave = false
Note that the instance's var autosave always has higher priority than the class var autosave, so you can set myMO?.autosave = nil in order to use the default autosave setting of the class.
I would simply set your view controller as the delegate for both the textfield and textview. In an iOS environment you would add the protocol UITextFieldDelegate and UITextViewDelegate to your view controller header file and implement the methods - (void)textFieldDidEndEditing:(UITextField *)textField and - (void)textViewDidEndEditing:(UITextView *)textView for the UITextField and UITextView respectively.
As an alternative on a UITextField (iOS), there is a delegate method named - (BOOL)textFieldShouldReturn:(UITextField *)textField which is called whenever the 'enter' key is pressed on a UITextField.
In a Mac OSX environment you would add the appropriate protocol to your view controller header file (for NSTextView add the NSTextDelegate, for NSTextField add the NSControlTextEditingDelegate) and then implement the appropriate methods: -(void)textDidChange:(NSNotification *)notification for an NSTextView and - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor for an NSTextField.
You can do any kind of validation you need to in those methods and then do a call to [myMOC save:&error]; before you return.
When ever you edit a row within that method write this code
[[NSNotificationCenter defaultCenter] addObserver:self selector:(autosaveCoreData:) name:nil object:your_tableview_object];
-(void)autosaveCoreData:(Event*)event{
event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:_managedObjectContext];
[event setValue:Attribute_Value forKey:#"your atttribute"];
NSError *error;
if (![_managedObjectContext save:&error]) {
}
}
}
I hope this may solve your problem

Command-Key-Up Cocoa

I'm trying to mimic the functionality of the cmd-tab keyboard shortcut where the user can switch between applications hitting a certain key and then when they release command something happens.
I'm using this code right now but it can only detects keydown. I need this to fire on key up
- (void)flagsChanged:(NSEvent *)theEvent {
if ([theEvent modifierFlags] & NSCommandKeyMask) {
NSLog(#"Do my stuff here");
}
}
Thanks
According to the docs:
Informs the receiver that the user has pressed or released a modifier
key (Shift, Control, and so on).
What you need to do here is when you get the event in which the command key goes down, you need to set a flag somewhere, and in subsequent calls, check for the absence of the command key being down.
For instance, assuming you have an ivar called _cmdKeyDown:
- (void)flagsChanged:(NSEvent *)theEvent
{
[super flagsChanged:theEvent];
NSUInteger f = [theEvent modifierFlags];
BOOL isDown = !!(f & NSCommandKeyMask);
if (isDown != _cmdKeyDown)
{
NSLog(#"State changed. Cmd Key is: %#", isDown ? #"Down" : #"Up");
_cmdKeyDown = isDown;
}
}

NSTextField autocomplete

Does anyone know of any class or lib that can implement autocompletion to an NSTextField?
I'am trying to get the standard autocmpletion to work but it is made as a synchronous api. I get my autocompletion words via an api call over the internet.
What have i done so far is:
- (void)controlTextDidChange:(NSNotification *)obj
{
if([obj object] == self.searchField)
{
[self.spinner startAnimation:nil];
[self.wordcompletionStore completeString:self.searchField.stringValue];
if(self.doingAutocomplete)
return;
else
{
self.doingAutocomplete = YES;
[[[obj userInfo] objectForKey:#"NSFieldEditor"] complete:nil];
}
}
}
When my store is done, i have a delegate that gets called:
- (void) completionStore:(WordcompletionStore *)store didFinishWithWords:(NSArray *)arrayOfWords
{
[self.spinner stopAnimation:nil];
self.completions = arrayOfWords;
self.doingAutocomplete = NO;
}
The code that returns the completion list to the nstextfield is:
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
{
*index = -1;
return self.completions;
}
My problem is that this will always be 1 request behind and the completion list only shows on every 2nd char the user inputs.
I have tried searching google and SO like a mad man but i cant seem to find any solutions..
Any help is much appreciated.
Instead of having the boolean property doingAutocomplete, make the property your control that made the request. Let's call it autoCompleteRequestor:
#property (strong) NSControl* autoCompleteRequestor;
So where you set your current property doingAutocomplete to YES, instead store a reference to your control.
- (void)controlTextDidChange:(NSNotification *)obj
{
if([obj object] == self.searchField)
{
[self.spinner startAnimation:nil];
[self.wordcompletionStore completeString:self.searchField.stringValue];
if(self.autoCompleteRequestor)
return;
else
{
self.autoCompleteRequestor = [[obj userInfo] objectForKey:#"NSFieldEditor"];
}
}
}
Now when your web request is done, you can call complete: on your stored object.
- (void) completionStore:(WordcompletionStore *)store didFinishWithWords:(NSArray *)arrayOfWords
{
[self.spinner stopAnimation:nil];
self.completions = arrayOfWords;
if (self.autoCompleteRequestor)
{
[self.autoCompleteRequestor complete:nil];
self.autoCompleteRequestor = nil;
}
}
NSTextView has the functionality of completing words of partial words.
Take a look at the documentation for this component.
Maybe you can switch to this component in your application.

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.

Uiswitch on/off

m new to objective-c, i have made a application of login page in which i have used UISwitch to remember d login details if switch is in on mode. i have done with to remember the login details but problem is that how to use the switch on/off condition. Thanx in advance
The easiest solution of all :)
if (switchValue.on){
//Remember Login Details
}
else{
//Code something else
}
You would add a conditional statement somewhere in your code depending on the switch's on property. Let's say, for example, that you remember login details in a method called rememberLoginDetails. What you would do is, when some action is triggered (the user leaves the login page, for example):
if([yourSwitch isOn]) {
[self rememberLoginDetails];
} else {
// Do nothing - switch is not on.
}
The important method here is the isOn method for the UISwitch yourSwitch. isOn is the getter for the switch's on property, which is a BOOL property containing YES if the switch is on, and NO if it is not.
For more information, you can see the UISwitch class reference, specifically the part about isOn.
if you want to remember the login details just the moment where the user TURN ON the switch, you can done it by create an Action.
- (IBAction)yourSwitch:(id)sender {
if([sender isOn]){
//do your stuff here
}else{
}
}
I believe the code needs to be this:
if([yourSwitch isOn] == YES) {
[self rememberLoginDetails];
} else {
// Do nothing - switch is not on.
}
This is another solution, if your UISwitch is in a tableView
1 add this code in "tableView:cellForRowAtIndexPath:" method
[switchControl addTarget:self action:#selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
2 add this method
- (void) switchChanged:(id)sender {
UISwitch *switchControl = sender;
NSLog( #"The switch is %#", switchControl.on ? #"ON" : #"OFF" );
}
if(switchValue.isOn){
[switchValue setOn:NO];
} else {
[switchValue setOn:YES];
}
i had the same problem, i had the name of the UISwitch = Selected
i changed it to another name and it worked.
This is another solution for that question.
if (switchValue.on == YES)
{
// Code...
}
else
{
// Other code...
}