Select next NSTextField with Tab key in Swift - swift

Is there a way to change the responder or select another textfield by pressing tab on the keyboard, in Swift?
Notes:
It's for a fill in the blank type application.
My VC creates a list of Words [Word], and each of those words has its own WordView - word.wordView. The WordView is what is displayed. WordView is a child of NSTextField.
I tried to override keydown but it doesn't allow me to type anything in the text view.

You have to connect your textField nextKeyView to the next textField through the IB or programmatically:
textField1.nextKeyView = textField2

Assuming you want to go from textView1 to textView2. First set the delegate:
self.textView1.delegate = self
Then implement the delegate method:
func textView(textView: NSTextView, doCommandBySelector commandSelector: Selector) -> Bool {
if commandSelector == "insertTab:" && textView == self.textView1 {
self.window.makeFirstResponder(self.textView2)
return true
}
return false
}

If you want some control over how your field tabs or moves with arrow keys between fields in Swift, you can add this to your delegate along with some move meaningful code to do the actual moving like move next by finding the control on the superview visibly displayed below or just to the right of the control and can accept focus.
public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
switch commandSelector {
case #selector(NSResponder.insertTab(_:)), #selector(NSResponder.moveDown(_:)):
// Move to the next field
Swift.print("Move next")
return true
case #selector(NSResponder.moveUp(_:)):
// Move to the previous field
Swift.print("Move previous")
return true
default:
return false
}
return false // I didn't do anything
}

I had the same or a similar problem, in that I wanted to use an NSTextView field, to allow multiple lines of text to be entered, but it was the sort of field where entering a tab character would make no sense. I found an easy fix for this: NSTextView has an instance property of isFieldEditor, which is set to false by default; simply set this to true, and tabs will now skip to the next field.

Related

How to make a NSTextView, that is added programmatically, active?

I am making an app where a user can click anywhere on the window and a NSTextView is added at the mouse location. I have got it working with the below code but I am not able to make it active (in focus) after adding it to the view (parent view). I have to click on the NSTextView to make it active but this is not what I want. I want it to automatically become active when its added to the parent view.
Code in my ViewController to add the NSTextView to its view:
private func addText(at point: NSPoint) {
let textView = MyTextView(frame: NSRect(origin: point, size: CGSize(width: 150.0, height: 40.0)))
view.addSubview(textView)
}
MyTextView class looks like below:
class MyTextView: NSTextView {
override var shouldDrawInsertionPoint: Bool {
true
}
override var canBecomeKeyView: Bool {
true
}
override func viewWillDraw() {
isHorizontallyResizable = true
isVerticallyResizable = true
insertionPointColor = .red
drawsBackground = false
isRichText = false
allowsUndo = true
font = NSFont.systemFont(ofSize: 40.0)
}
}
Also, I want it to lose focus (become inactive) when some other elements (view) are clicked. Right now, once a NSTextView becomes active, it stays active no matter what other elements I click except when I click on an empty space to create yet another NSTextView.
I have gone through the Apple docs multiple times but I think I am missing something. Any help would be much appreciated.
Get the NSWindow instance of the NSViewController's view and call makeFirstResponder passing the text view as parameter.
To lose focus call makeFirstResponder passing nil.

Check for exiting TextField resignedFirstReponder?

Is it possible to check if a TextField has been resigned?
I have 3 TextFields, and Cycle through them with return key with (resignFirstResponder) and (becomeFirstResponder)
But is there a way to check if a field has resigned? I noticed some of my app testers don’t use return key but click manually on the fields, and that way my previous field doesn’t save data the way it should.
What’s the best way to check if a user clicked away from a TextField ? Either on to a next text field or temporary away ?
isFirstResponder Apple Docs
Returns a Boolean value indicating whether this object is the first responder.
var isFirstResponder: Bool { get }
how to use
if txtField.isFirstResponder {
// do whatever you want to
}
If you want to save data when textField change focus .. you should implement delegate method of text field
Before resigning as first responder, the text field calls its
delegate’s textFieldShouldEndEditing(_:) method. Use that method to
validate the current text.
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool
in your case yo can check which texfield ends editing
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if textField == textfield1 {
// save
}
}
Apple Docs for textFieldShouldEndEditing

First responder on mouse down behavior NSControl and NSView

I have a custom control. If it inherits from NSView, it automatically becomes the first responder when I click on it. If it inherits from NSControl, it does not. This difference in behavior persists, even if I override mouseDown(with:) and don't call super.
Code:
class MyControl: NSView {
override var canBecomeKeyView: Bool { return true }
override var acceptsFirstResponder: Bool { return true }
override func drawFocusRingMask() { bounds.fill() }
override var focusRingMaskBounds: NSRect { return bounds }
override func draw(_ dirtyRect: NSRect) {
NSColor.white.set()
bounds.fill()
}
}
As you can see, I override acceptsFirstResponder among other methods and properties that are key view and responder related. I have also checked the refusesFirstResponder property. It is set to false.
What is the reason for this difference in behavior?
Is there a method or property that I can override to influence it?
Say I want the behavior where the view becomes the first responder when clicked and the view inherits from NSControl, is calling window!.makeFirstResponder(self) at the beginning of my mouse-down event handler a good solution or is there a better one?
The property to override is needsPanelToBecomeKey.
A Boolean value indicating whether the view needs its panel to become the key window before it can handle keyboard input and navigation.
The default value of this property is false. Subclasses can override this property and use their implementation to determine if the view requires its panel to become the key window so that it can handle keyboard input and navigation. Such a subclass should also override acceptsFirstResponder to return true.
This property is also used in keyboard navigation. It determines if a mouse click should give focus to a view—that is, make it the first responder). Some views (for example, text fields) want to receive the keyboard focus when you click in them. Other views (for example, buttons) receive focus only when you tab to them. You wouldn't want focus to shift from a textfield that has editing in progress simply because you clicked on a check box.
NSView returns true, NSControl returns false.

TextField user interaction disabled, but display an error view when touched

I have a UITextField and in my ViewController's code it is set depending on it's value to
textField.isUserInteractionEnabled = false
or
textField.isUserInteractionEnabled = true
Now, when user interaction is disabled, I would like it still to react to touches and show an error message (e.g. unhide another view), which tells the user that editing this text field is not possible.
How can I achieve this in the most lean way? This solution here (https://stackoverflow.com/a/9117285) suggests to not disable user interaction, but reject content changes - which is what I don't want (the keyboard should not show up - it won't show up when user interaction is disabled, but I can't react to touches either).
You either need to add a view a bove the textfield when it's disabled with a gesture to show the appropriate message , or do this
NotificationCenter.default.addObserver(self, selector: #selector(keyShow), name:UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyShow ( _ not:NSNotification) {
if shouldHideKeyB {
self.view.endEditing(true)
// show disabled message
}
}
where shouldHideKeyB is the current state of the textfield
Instead of using isUserInteractionEnabled you could implement your own isDisabled Bool and UITextFieldDelegate and implement func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool. When isDisabled is true show the error view and return false, otherwise return true. In the didSet of isDisabled you can hide the error view.
Returning false from this method should stop the keyboard from popping up and will still allow you to interact with the view.

Prevent user from setting cursor position on UITextField

I have an UITextField constructed using the storyboard. I want to not allow the user to change the position of the cursor and keep it always at the end of the text into the text field.
I tried to change the position of the cursor at the touchdown event, but when selecting the text field and then change the position of the cursor by touching the text field again, the position is changed:
- (IBAction)amountBoxTouchDown:(id)sender {
UITextPosition *start = [amountBox positionFromPosition:[amountBox beginningOfDocument] offset:amountBox.text.length];
UITextPosition *end = [amountBox positionFromPosition:start
offset:0];
[amountBox setSelectedTextRange:[amountBox textRangeFromPosition:start toPosition:end]];
}
Does anyone know a solution? Thanks
Simply create a subclass of UITextField and override the closestPositionToPoint method:
- (UITextPosition *)closestPositionToPoint:(CGPoint)point{
UITextPosition *beginning = self.beginningOfDocument;
UITextPosition *end = [self positionFromPosition:beginning offset:self.text.length];
return end;
}
Now the user will be unable to move cursor, it will be always in the end of field.
SWIFT:
override func closestPosition(to point: CGPoint) -> UITextPosition? {
let beginning = self.beginningOfDocument
let end = self.position(from: beginning, offset: self.text?.count ?? 0)
return end
}
Disable any gesture recognizers on the text field after it has become first responder. This allows it to receive the initial tap, but prevents the user from interacting with the field while it is the first responder. This keeps the system behavior of keeping the cursor at the end of the text without allowing the user to override it.
In a UITextField subclass, add the following:
SWIFT 3.1:
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return !isFirstResponder
}
In your storyboard, change the class of your text field to your UITextField subclass.
If you are interested in always keeping your cursor at the end of the text, do this:
override func closestPosition(to point: CGPoint) -> UITextPosition? {
return self.endOfDocument
}
Think in layers and of controls as tools that you can combine to achieve functionality.
If you simply place a UIButton over top a UITextField and change the button type to Custom, you can prevent all touch events on the text field such as moving the cursor, selecting text, copying, cutting, and pasting.
By default, a custom button is transparent.
Create an action so that when the button is touched, the text field becomes the first responder.
Ok, what you have to do is have a UITextField that is hidden.
Add that hidden text field to the view, and call becomeFirstResponder on it. From your amountBoxTouchDown: method.
In the Textfield delegate, take the text the user typed in and add it to amountBox.text. Also turn off userInteractionEnabled for the visible amountBox textField.
This creates the effect you desire.
Have a look at for some sample code Link.
Extension of #kas-kad's solution. I created a subclass of UITextView, with this
var enableCursorMotion = true
override func closestPosition(to point: CGPoint) -> UITextPosition? {
if enableCursorMotion {
return super.closestPosition(to: point)
}
else {
let beginning = self.beginningOfDocument
let end = self.position(from: beginning, offset: (self.text?.count)!)
return end
}
}
Why not use the textFieldDidChangeSelection method from UITextFieldDelegate?
With the below implementation the cursor is always in the end.
And you don't have to create a subclass of your UITextField
func textFieldDidChangeSelection(_ textField: UITextField) {
let position = textField.endOfDocument
textField.selectedTextRange = textField.textRange(from: position, to: position)
}
A drawback of this is that you can't mark the text or even see the menu with select, paste etc options.