Deselect the text in a NSTextField - swift

Is there an easy way to deselect an NSTextField after pressing enter?

First you will need to make your view controller the delegate of your text field. Then you override NSControl instance method controlTextDidEndEditing(_:), get your textfield current editor selected range
and from the main thread set it back to your textfield:
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
override func controlTextDidEndEditing(_ obj: Notification) {
if let selectedRange = textField.currentEditor()?.selectedRange {
DispatchQueue.main.async {
self.textField.currentEditor()?.selectedRange = selectedRange
}
}
}
}

Here is one way I did it.
By disabling it with isSelectable and isEditable and then setting a timer to re-enable it after 0.5s
#IBAction func timeCodeChanged(_: NSTextField) {
timecodeLabel.isSelectable = false
timecodeLabel.isEditable = false
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(reEnableLabel), userInfo: nil, repeats: false)
}
#objc func reEnableLabel() {
timecodeLabel.isSelectable = true
timecodeLabel.isEditable = true
}

Related

Run Counter while button press

I am very new to swift and I want to make a button work like this:
Press the Button and hold. While the Button is pressed the label value goes up like every second +1 until the button is released.
This is what I get so far:
class ViewController: UIViewController {
var counter = 0;
override func viewDidLoad() {
super.viewDidLoad();
}
#IBOutlet weak var label: UILabel!
#IBAction func btn(_ sender: Any) {
if (sender as AnyObject).state != .ended{
counter+=1;
// wait 100ms
self.label.text = String (counter);
}
}
}
This is how I linked it:
You can achieve this using the UIButton actions Touch Down and Touch Up Inside
class ViewController: UIViewController {
var timer : Timer?
var startTime = 0
var timerReset = true // I don't know what logic you want. This basically has been added so the number is not set to 0 immediately when you release the button. You can also add another button to reset startTime variable and the label
#IBOutlet weak var numberLabel: UILabel!
#IBOutlet weak var numberButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
numberLabel.text = String(startTime) //initial value
// Do any additional setup after loading the view.
}
#IBAction func holdingTheButton(_ sender: Any) {
print("I am holding")
timerReset = false // reset to false since you are holding the button
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#IBAction func buttonReleased(_ sender: Any) {
print("button released")
startTime = 0
timer?.invalidate()
timer = nil
timerReset = true // reset to true since you released.
}
#objc func updateTime(){
//update label every second
print("updating label ")
if timerReset {
startTime = 0
}
startTime += 1
numberLabel.text = String(startTime)
}
}
IMPORTANT: Make sure you are connecting in the right way. Touch Down has to be used if you want to call the function while you hold the button:
In your console, you should see this happening if you release the button after 10 SECONDS:
If you want to have a button to reset, you can just add the it and then connect it to the following function (but also make sure you remove the bool timeReset and the if statement inside updateTime:
#IBAction func resetTimer(_ sender: Any) {
startTime = 0
numberLabel.text = String(startTime)
}
You can achieve it using two sent event touch of UIButton and a Timer.
var counter = 0
var timer: Timer?
#IBAction func buttonTouchUpInside(_ sender: Any) {
timer?.invalidate()
print(counter)
}
#IBAction func buttonTouchDown(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(increaseCount), userInfo: nil, repeats: true)
}
#objc func increaseCount() {
counter += 1
print(counter)
}

how to get a textview to autoscroll down when theres a new line

in swift 4.0 for cocoa applications
how do you get a textview to autoscroll down when theres a new line
of text added into it?
is there a built in function for this?
i can't seem to find a way to do this.
chatplace.scroll(<#T##point: NSPoint##NSPoint#>)
Here's a simple example that works:
import UIKit
class ViewController: UIViewController {
var count = 0
var timer:Timer?
#IBOutlet weak var textView: UITextView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textView.isScrollEnabled = true
textView.becomeFirstResponder()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer(timeInterval: 0.125, repeats: true, block: { (timer) in
self.count += 1
self.textView.text.append("\n\(self.count) This is another line")
})
if let timer = timer {
RunLoop.current.add(timer, forMode: .commonModes)
}
}
}
The result:
If you want to scroll programmatically this should do it:
func scrollToEnd(_ someTextView:UITextView) {
let bottom = NSMakeRange(someTextView.text.lengthOfBytes(using: .utf8)-1, 1)
someTextView.scrollRangeToVisible(bottom)
}

Textfield didChange with timer

I'm working on autocompletion in my project and I would like to detect when the textfieldDidChange value and call a method (link to API) 500MS after that.
I hope it's clear enough
Thank for your help !
In Swift 3, you probably want to connect to "editing changed" not "value changed", and reset the timer and start another timer:
weak var timer: Timer?
#IBAction func didChangeEditing(_ sender: UITextField) {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] timer in
// trigger your autocomplete
}
}
Or you can alternatively hook into shouldChangeCharactersIn. For example:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self // or you can do this in IB
}
weak var timer: Timer?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
timer?.invalidate() // cancel prior timer, if any
timer = .scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] timer in
// trigger your autocomplete
}
return true
}
}
First make sure to make your class the TextField's delegate:
class yourClass: UIViewController, UITextFieldDelegate{
//...
}
And then in viewDidLoad():
textField.delegate = self
numbersTextField.addTarget(self, action: #selector(self.textFieldDidChange), for: .editingChanged)
After that, you can use a timer, like this:
var timer = Timer()
var count = 0
func textFieldDidChange(textField: UITextField){
timer.invalidate()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.timerFunc), userInfo: nil, repeats: true)
}
func timerFunc(){
count += 1
if count == 5{
timer.invalidate()
// do your things here down here, I assumed that your method is called autocomplete
autocomplete()
}
}
Hope it helps!

onClick Action with Label does not work [duplicate]

I would like to make a UILabel clickable.
I have tried this, but it doesn't work:
class DetailViewController: UIViewController {
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
...
let tap = UITapGestureRecognizer(target: self, action: Selector("tapFunction:"))
tripDetails.addGestureRecognizer(tap)
}
func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
}
}
Have you tried to set isUserInteractionEnabled to true on the tripDetails label? This should work.
Swift 3 Update
Replace
Selector("tapFunction:")
with
#selector(DetailViewController.tapFunction)
Example:
class DetailViewController: UIViewController {
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
...
let tap = UITapGestureRecognizer(target: self, action: #selector(DetailViewController.tapFunction))
tripDetails.isUserInteractionEnabled = true
tripDetails.addGestureRecognizer(tap)
}
#objc
func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
}
}
SWIFT 4 Update
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(GameViewController.tapFunction))
tripDetails.isUserInteractionEnabled = true
tripDetails.addGestureRecognizer(tap)
}
#objc func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
}
Swift 5
Similar to #liorco, but need to replace #objc with #IBAction.
class DetailViewController: UIViewController {
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
...
let tap = UITapGestureRecognizer(target: self, action: #selector(DetailViewController.tapFunction))
tripDetails.isUserInteractionEnabled = true
tripDetails.addGestureRecognizer(tap)
}
#IBAction func tapFunction(sender: UITapGestureRecognizer) {
print("tap working")
}
}
This is working on Xcode 10.2.
Swift 3 Update
yourLabel.isUserInteractionEnabled = true
Good and convenient solution:
In your ViewController:
#IBOutlet weak var label: LabelButton!
override func viewDidLoad() {
super.viewDidLoad()
self.label.onClick = {
// TODO
}
}
You can place this in your ViewController or in another .swift file(e.g. CustomView.swift):
#IBDesignable class LabelButton: UILabel {
var onClick: () -> Void = {}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
onClick()
}
}
In Storyboard select Label and on right pane in "Identity Inspector" in field class select LabelButton.
Don't forget to enable in Label Attribute Inspector "User Interaction Enabled"
You need to enable the user interaction of that label.....
For e.g
yourLabel.userInteractionEnabled = true
For swift 3.0 You can also change gesture long press time duration
label.isUserInteractionEnabled = true
let longPress:UILongPressGestureRecognizer = UILongPressGestureRecognizer.init(target: self, action: #selector(userDragged(gesture:)))
longPress.minimumPressDuration = 0.2
label.addGestureRecognizer(longPress)
Pretty easy to overlook like I did, but don't forget to use UITapGestureRecognizer rather than UIGestureRecognizer.
Thanks researcher
Here's my solution for programmatic user interface using UIKit.
I've tried it only on Swift 5. And It worked.
Fun fact is you don't have to set isUserInteractionEnabled = true explicitly.
import UIKit
open class LabelButon: UILabel {
var onClick: () -> Void = {}
public override init(frame: CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
public convenience init() {
self.init(frame: .zero)
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
onClick()
}
}
Uses:
override func viewDidLoad() {
super.viewDidLoad()
let label = LabelButton()
label.text = "Label"
label.onClick = {
// TODO
}
}
Don't forget to set constraints. Otherwise it won't appear on view.
On top of all of the other answers, this depends on where the label is, it might be behind some subviews. You might think you tap on the label but maybe click the top view. To solve this you can bring the label view to the front with the following line.
self.view.bringSubviewToFront(lblView)
As described in the above solution
you should enable the user interaction first and add the tap gesture
this code has been tested using
Swift4 - Xcode 9.2
yourlabel.isUserInteractionEnabled = true
yourlabel.addGestureRecognizer(UITapGestureRecognizer(){
//TODO
})

MouseMoved function is never called

I'm trying to override mouseMoved function in NSViewController.
import Cocoa
class MainViewController: NSViewController {
override var acceptsFirstResponder: Bool {get {return true} }
#IBOutlet var background: RandomNumberBackground!
override func viewDidLoad() {
super.viewDidLoad()
}
override func awakeFromNib() {
NSTimer.scheduledTimerWithTimeInterval(0.04, target: background, selector: "updateNumbers", userInfo: nil, repeats: true)
}
#IBAction func btnLevelClicked(sender: AnyObject) {
self.presentViewControllerAsSheet(LevelScrollController())
}
override func mouseMoved(theEvent: NSEvent) {
Swift.print("MOVED!")
}
}
I've overrided acceptsFirstResponder but mouseMoved is never called. Why? Where I go wrong?
You need to set acceptsMouseMovedEvents in the windows property.
Add the following code to applicationDidFinishLaunching
func applicationDidFinishLaunching(aNotification: NSNotification) {
window.acceptsMouseMovedEvents = true
}
No need to set anything on the window.
A tracking area is the proper way to handle this.
Just add this in the VC's vieWDidLoad() method.
let ta = NSTrackingArea(rect: CGRect.zero, options: [.activeAlways, .inVisibleRect, .mouseMoved], owner: self, userInfo: nil)
self.view.addTrackingArea(ta)
This is the code I add when I need to track mouse moves in a view.
It is longer than previous answers, but this is what it takes to be done properly :)
class MyView: NSView {
var trackingArea: NSTrackingArea?
// MARK: - Tracking area management
/// Will install tracking area on the view if a window is set
override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
installTrackingArea()
}
/// Install tracking area if window is set, remove previous one if needed.
func installTrackingArea() {
guard let window = window else { return }
window.acceptsMouseMovedEvents = true
if trackingArea != nil { removeTrackingArea(trackingArea!) }
let trackingOptions = [.activeAlways, .mouseEnteredAndExited, .mouseMoved]
trackingArea = NSTrackingArea(rect: bounds,
options: trackingOptions,
owner: self, userInfo: nil)
self.addTrackingArea(trackingArea!)
}
/// Called when layout is modified
override func updateTrackingAreas() {
super.updateTrackingAreas()
installTrackingArea()
}
//MARK: - Mouse Events handling
override func mouseExited(with event: NSEvent) {
print("Good bye mouse")
}
override func mouseEntered(with event: NSEvent) {
let point = self.convert(event.locationInWindow, from: nil)
print("Hello mouse, welcome at \(point)")
}
override func mouseMoved(with event: NSEvent) {
let point = self.convert(event.locationInWindow, from: nil)
print("Mouse moved \(point)")
}
}
You need to set the acceptsMouseMovedEvents property on the window the view belongs to. See
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWindow_Class/index.html#//apple_ref/occ/instp/NSWindow/acceptsMouseMovedEvents
In swift 3 you can put this in your viewDidLoad()
self.view!.window?.acceptsMouseMovedEvents = true;