Execute an Action when the Enter-Key is pressed in a NSTextField? - cocoa

I have a small problem right now. I want to execute a method when the Enter key is pressed in a NSTextField. The user should be able to enter his data and a calculation method should be executed as soon as he hits the enter key.

You can do this by setting the text field's action. In IB, wire the text field's selector to your controller or whatever object presents the IBAction you want to use.
To set it in code, send the NSTextField a setTarget: message and a setAction: message. For example, if you're setting this on your controller object in code, and your textField outlet is called myTextField:
- (void)someAction:(id)sender
{
// do something interesting when the user hits <enter> in the text field
}
// ...
[myTextField setTarget:self];
[myTextField setAction:#selector(someAction:)];

You have to do only this
For some keys (Enter, Delete, Backspace, etc)
self.textfield.delegate = self;
and then implement this method
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
NSLog(#"Selector method is (%#)", NSStringFromSelector( commandSelector ) );
if (commandSelector == #selector(insertNewline:)) {
//Do something against ENTER key
} else if (commandSelector == #selector(deleteForward:)) {
//Do something against DELETE key
} else if (commandSelector == #selector(deleteBackward:)) {
//Do something against BACKSPACE key
} else if (commandSelector == #selector(insertTab:)) {
//Do something against TAB key
} else if (commandSelector == #selector(cancelOperation:)) {
//Do something against Escape key
}
// return YES if the action was handled; otherwise NO
}

The Swift 3 / 4 / 5 version of #M.ShuaibImran's solution:
First subclass your ViewController to: NSTextFieldDelegate
class MainViewController: NSViewController, NSTextFieldDelegate {
...
}
Assign the textField delegate to the ViewController in your viewDidLoad():
self.textField.delegate = self
Include the NSTextFieldDelegate method that handles keyboard responders:
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
// Do something against ENTER key
print("enter")
return true
} else if (commandSelector == #selector(NSResponder.deleteForward(_:))) {
// Do something against DELETE key
return true
} else if (commandSelector == #selector(NSResponder.deleteBackward(_:))) {
// Do something against BACKSPACE key
return true
} else if (commandSelector == #selector(NSResponder.insertTab(_:))) {
// Do something against TAB key
return true
} else if (commandSelector == #selector(NSResponder.cancelOperation(_:))) {
// Do something against ESCAPE key
return true
}
// return true if the action was handled; otherwise false
return false
}

In your delegate (NSTextFieldDelegate), add the following:
-(void)controlTextDidEndEditing:(NSNotification *)notification
{
// See if it was due to a return
if ( [[[notification userInfo] objectForKey:#"NSTextMovement"] intValue] == NSReturnTextMovement )
{
NSLog(#"Return was pressed!");
}
}

It's very easy and you can do it directly from UI editor
Right click the Text Feild, drag Action reference to the button as shown below in the screenshot
Now it will give you some option as shown in screen shot below, you need to select perform click
Now it should look like this
Note: The event will be raised as soon as you press Tab or Enter key. In case you want the action should only be raised when user presses the Enter key then you have to do a setting. Go to the Attribute inspector and change the Action property to Send on Enter only as shown in screen shot below

NSTextFieldDelegate's – control:textView:doCommandBySelector: is your friend.
Look for the insertNewline: command.

In Interface Builder - click on your NSTextField, go to the connections editor, drag from selector to your controller object - you're actions should come up!

Best way to do that is to bind the NSTextField value with NSString property.
For Example,define a method:
(void)setTextFieldString:(NSString *)addressString {}
add bindings:
[textField bind:#"value" toObject:self withKeyPath:#"self.textFieldString" options:nil];
Enter any text and hit the return key, setTextFieldString will be called.

Related

Keep both NSSplitViewController's child controllers in first responder chain

I've a document based app with an NSSplitViewController as the main window's content view controller.
The left pane contains a custom view with controller, which implements some menu commands.
The right pane contains a standard NSTableView with controller.
When the app starts the menu commands work as expected, but as soon as anything inside the right table view is selected, the menu commands get disabled.
How can I make sure that the view controller of the left pane remains inside the first responder chain?
I tried hooking up the menu commands directly to the correct view controller, but IB does not allow connections to another scene in a storyboard. I can only connect to objects in the same scene.
Regards,
Remco Poelstra
Connect to First Responder.
You can have all child view controllers respond to actions by implementing -[NSResponder supplementalTargetForAction:sender:] in your NSSplitViewController subclass:
- (id)supplementalTargetForAction:(SEL)action sender:(id)sender
{
id target = [super supplementalTargetForAction:action sender:sender];
if (target != nil) {
return target;
}
for (NSViewController *childViewController in self.childViewControllers) {
target = [NSApp targetForAction:action to:childViewController from:sender];
if (![target respondsToSelector:action]) {
target = [target supplementalTargetForAction:action sender:sender];
}
if ([target respondsToSelector:action]) {
return target;
}
}
return nil;
}
In Swift 4 you can do the following:
override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
for childViewController in childViewControllers {
if childViewController.responds(to: action) {
return childViewController
} else {
guard let supplementalTarget = childViewController.supplementalTarget(forAction: action, sender: sender) else {
continue
}
return supplementalTarget
}
}
return super.supplementalTarget(forAction: action, sender: sender)
}

Detecting key press event in Swift

I'm trying to find a way to detect if a key (on a keyboard) has been pressed on Swift. Any ideas and suggestions will be greatly appreciated.
Since you updated your question and you wanted to know how to do this for a window, here's an answer. Subclass NSWindow and use this subclass instead.
Your custom class should look like this:
import Cocoa
class EditorWindow: NSWindow {
override func keyDown(event: NSEvent) {
super.keyDown(event)
Swift.print("Caught a key down: \(event.keyCode)!")
}
}
If you've made your window in Interface Builder/XCode, click the window object and go to the Attribute Inspector (⌥+⌘+3). The Attribute Inspector will be in the sidebar on the right. Making sure your window is selected in Interface Builder, at the top of the Attribute Inspector in the Custom Class area put your new class in the class input.
In order to communicate the event from the this window class to my app I add a function to the window that accepts a callback function that I then store in an array of callback functions. I get access to this window through the AppDelegate which can get a weak reference to the current main window. Then in the above function I iterate overall the callbacks and call it with the NSEvent as the argument. I also first check to see if any command keys like the option keys are being pressed first through modifierFlags property on the event. It ends up looking like this:
import Cocoa
typealias Callback = (NSEvent) -> ()
class KeyCaptureWindow: NSWindow {
var keyEventListeners = Array<Callback>()
override func keyDown(event: NSEvent) {
if event.modifierFlags.contains(NSEventModifierFlags.CommandKeyMask) {
super.keyDown(event)
return
}
for callback in keyEventListeners {
callback(event)
}
}
func addKeyEventCallback(callback: Callback) {
keyEventListeners.append(callback)
}
}
And then elsewhere in my code I have a line like so:
let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
let mainWindow = appDelegate.getWindow()
mainWindow.addKeyEventCallback(handleKeyEvent)
I added the getWindow method to my app delegate class. This method returns the NSWindow cast to KeyCaptureWindow. There may be a better way to do all this but this works for me. Another way to possibly do this is to use first responders and NSView, but that's not how I've been doing it.
You have to override the keyDown-method.
var direction:String = ""
override func keyDown(theEvent: NSEvent!) // A key is pressed
{
if theEvent.keyCode == 123
{
direction = "left" //get the pressed key
}
else if theEvent.keyCode == 124
{
direction = "right" //get the pressed key
}
println("Key with number: \(theEvent.keyCode) was pressed")
}

How do you get the return key to execute code in Swift?

In xcode, using Swift, I need to be able to execute a line of code using the return key on the software keyboard.
How do I specify this?
I know how to resignFirstResponder for the software keyboard but that is all at the moment.
for a UITextField:
a UITextFieldDelegate has the method textFieldShouldReturn: that is called . Do your stuff and return NO to prevent a newline / yes for multiline
- (BOOL)textFieldShouldReturn:(UITextField*)field {
//DO your stuff
return NO; // yes to allow enter
}
for a UITextView:
the delegate has - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
check if text is return and do the above.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ( [text isEqualToString:#"\n"] ) {
//Do whatever you want
}
return YES;
}

UITextField Delegate Return on mulitple text fields

Newbie here,
I have 4 textfields on my single-view app example (address, city, state and zip). I'm trying to use delegation from each to dismiss the keyboard when the user taps out of each. I can't have two identically named methods.
Here's the method to dismiss the address textfield:
-(BOOL) textFieldShouldReturn:(UITextField *)address
{
if (address == self.address)
{
[address resignFirstResponder];
}
return YES;
}
So, my return key can dismiss the keyboard only if the user is in the address textfield, but I can't figure out how to use delegation for the other textfields. The delegate protocol documentation didn't have any specifics on this.
thanks,
J.
And that's the use of the parameter passed in the textFieldShouldReturn delegate method.
If you have multiple text fields, the same delegate method will be called and the text field sender object is passed as the parameter.
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (textField == self.addressField)
{
//Do what you need to do if address field should return
}
else if (textField == self.cityField)
{
//Do what you need to do if city field should return
}
return YES;
}
But if what you want is just to resign the text field and since the text field is passed as the sender, you can just resign the passed text field:
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}

How to expand and collapse parts of NSSplitView programmatically?

I want to replace RBSplitView with NSSplitView in my existing project. The application is now leopard only and I would like to replace RBSplitView with the new NSSplitView shipped with Leopard.
However, I'm missing RBSplitView's handy methods expand and collapse in NSSplitView. How can I expand and collapse parts of NSSplitView programmatically?
Simply hide the subview you want to collapse, e.g.
[aSubViewToCollapse setHidden:YES];
You might also want to implement the delegate method -(BOOL)splitView:shouldHideDividerAtIndex: to return YES to hide the divider when a collapsed.
I just got programmatic expanding and collapsing of NSSplitView to work. I've also configured my NSSplitView to expand/collapse a subview whenever the divider is double-clicked, so I wanted this to play nice with that feature (and it seems to). This is what I did:
(in this example, splitView is the NSSplitView itself, splitViewSubViewLeft is the subview I wish to expand/collapse and lastSplitViewSubViewLeftWidth is an instance variable of type CGFloat.)
// subscribe to splitView's notification of subviews resizing
// (I do this in -(void)awakeFromNib)
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(mainSplitViewWillResizeSubviewsHandler:)
name:NSSplitViewWillResizeSubviewsNotification
object:splitView
];
// this is the handler the above snippet refers to
- (void) mainSplitViewWillResizeSubviewsHandler:(id)object
{
lastSplitViewSubViewLeftWidth = [splitViewSubViewLeft frame].size.width;
}
// wire this to the UI control you wish to use to toggle the
// expanded/collapsed state of splitViewSubViewLeft
- (IBAction) toggleLeftSubView:(id)sender
{
[splitView adjustSubviews];
if ([splitView isSubviewCollapsed:splitViewSubViewLeft])
[splitView
setPosition:lastSplitViewSubViewLeftWidth
ofDividerAtIndex:0
];
else
[splitView
setPosition:[splitView minPossiblePositionOfDividerAtIndex:0]
ofDividerAtIndex:0
];
}
I tried the solution above, and found it did not work, as isSubviewCollapsed never returned YES
A combination of the suggestions yielded a result which works
if ([splitViewTop isHidden]) {
[splitViewTop setHidden:NO];
[split
setPosition:previousSplitViewHeight
ofDividerAtIndex:0];
}
else {
[splitViewTop setHidden:YES];
}
[split adjustSubviews];
In El Capitan, this did the trick for me.
splitViewItem.collapsed = YES;
After some experimenting with the suggestions this was the easiest solution I found:
-(void)toggleCollapsibleView:(ib)sender {
[collapsibleView setHidden:![splitView isSubviewCollapsed:collapsibleView]];
[splitView adjustSubviews];
}
The function is a user defined first-responder action. It is triggered by a menu-item (or keystroke).
The collapsibleView is a subview in the splitView both of which are connected in IB with their properties.
In macOS Sierra, the collapsed property is changed to isCollapsed. Is straight forward just setting the property to true or false. The following code is from my WindowController, where I have two SplitViewItems.
#IBAction func toggleMap(_ sender: Any) {
if let splitViewController = contentViewController as? NSSplitViewController {
let splitViewItem = splitViewController.splitViewItems
if splitViewItem.first!.isCollapsed {
splitViewItem.first!.isCollapsed = false
} else if splitViewItem.last!.isCollapsed {
splitViewItem.last!.isCollapsed = false
} else {
if splitViewItem.first!.isCollapsed {
splitViewItem.first!.isCollapsed = false
}
splitViewItem.last!.isCollapsed = true
}
}
}
NSSplitView actually has a private method -(void)_setSubview:(NSView *)view isCollapsed:(BOOL)collapsed that does this. Those who would like to ignore all warnings against using private methods, behold:
- (void)toggleSubview:(NSView *)view {
SEL selector = #selector(_setSubview:isCollapsed:);
NSMethodSignature *signature = [NSSplitView instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
[invocation setArgument:&view atIndex:2];
BOOL arg = ![self isSubviewCollapsed:view];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];
}
I implemented this as a category on NSSplitView. The only issue is that Xcode gives a warning about _setSubview:isCollapsed: being undeclared... I'm not really sure how to get around that.
El Capitan Update
I haven't written any code for OS X in ~2 years now so I haven't been able to verify this, but according to lemonmojo in the comments below, _setSubview:isCollapsed: was renamed in El Capitan to _setArrangedView:isCollapsed:.
In swift this works
func togglePanel() {
let splitViewItem = self.mySplitView.arrangedSubviews
if mySplitView.isSubviewCollapsed(outline.view){
splitViewItem[0].hidden = false
} else {
splitViewItem[0].hidden = true
}
call this from IBAction,
outline is an OutlineViewController with own xib and we need the view hence outline.view, keeping it simple but hope you get the idea
#IBAction func segmentAction(sender: NSSegmentedControl) {
splitVC?.togglePanel(sender.selectedSegment)
}
and
func togglePanel(segmentID: Int) {
let splitViewItem = self.mySplitView.arrangedSubviews
switch segmentID {
case segmentID:
if mySplitView.isSubviewCollapsed(splitViewItem[segmentID]) {
splitViewItem[segmentID].hidden = false
} else {
splitViewItem[segmentID].hidden = true
}
default:
break
}
}
And implement delegate
func splitView(splitView: NSSplitView, shouldHideDividerAtIndex dividerIndex: Int) -> Bool {
return true
}
And with 10.11 you might just use toggleSidebar action method.
How to toggle visibility of NSSplitView subView + hide Pane Splitter divider?
https://github.com/Dis3buted/SplitViewController
I recommend to use NSSplitViewController instead, and NSSplitViewItem.isCollapsed to control them. This just work.
let item: NSSplitViewItem = ...
item.isCollapsed = true
To make this to work properly, you have to configure split-UI components with mainly view-controllers. Otherwise, it can be broken.
You could try Brandon Walkin's BWToolKit.
The BWSplitView class has a method
- (IBAction)toggleCollapse:(id)sender;
#IBOutlet weak var horizontalSplitView: NSSplitView!
var splitViewItem : [NSView]?
var isSplitViewHidden: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// To Hide Particular Sub-View.
splitViewItem = self.horizontalSplitView.arrangedSubviews
splitViewItem?[0].isHidden = true
isSplitViewHidden = true
}
//MARK: View / Manage All Jobs Button Tapped.
#IBAction func actionManageScheduleJobsButtonTapped(_ sender: Any) {
if isSplitViewHidden == true {
isSplitViewHidden = false
splitViewItem?[0].isHidden = false
} else {
isSplitViewHidden = true
splitViewItem?[0].isHidden = true
}
}
--------- OR ----------
//MARK: View / Manage All Jobs Button Tapped.
#IBAction func actionManageScheduleJobsButtonTapped(_ sender: Any) {
if splitViewItem?[0].isHidden == true {
splitViewItem?[0].isHidden = false
} else {
splitViewItem?[0].isHidden = true
}
}

Resources