Swift-4 MacOS Detect Mouse Wheel Scroll over NSTextField? - swift

I have a NSTextField on a MacOS app with Swift-4
Its purpose is for the user to input a number.
Is there a way to change the number based on scroll up or scroll down of a mouse wheel?

You can just override scrollWheel(with event: NSEvent) method and check if the event.deltaY is positive or negative and increase or decrease the textField value:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.stringValue = "0"
}
override func scrollWheel(with event: NSEvent) {
super.scrollWheel(with: event)
if var value = Int(textField.stringValue) {
if event.deltaY > 0 { value += 1 }
if event.deltaY < 0 { value -= 1 }
textField.stringValue = String(value)
print(value)
}
}
}
If you would like to just change the field that has focus you can use the view window fieldEditor:
if let text = view.window?.fieldEditor(false, for: nil) {
if var value = Int(text.string) {
if event.deltaY > 0 { value += 1 }
if event.deltaY < 0 { value -= 1 }
text.string = String(value)
print(value)
}
}
If you would like to restrict the scroll to just a particular field you would need to set your view controller as the delegate of it and compare its delegate against the textfield object:
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var textField1: NSTextField!
#IBOutlet weak var textField2: NSTextField!
override func viewDidAppear() {
super.viewDidAppear()
textField1.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
textField1.stringValue = "0"
textField2.stringValue = "0"
}
override func scrollWheel(with event: NSEvent) {
super.scrollWheel(with: event)
NSSound(named: "Tink")?.stop
if view.window?.firstResponder?.textView?.delegate === textField1,
let text = view.window?.fieldEditor(false, for: nil),
var value = Int(text.string) {
if event.deltaY < 0 { value += 1 }
if event.deltaY > 0 { value -= 1 }
NSSound(named: "Tink")?.play()
text.string = String(value)
print(value)
}
}
}
extension NSResponder {
var textView: NSTextView? { return self as? NSTextView }
}

Related

How to set NSSlider value in a NSToolbar - Swift Cocoa + Storyboard

I am quite new to Swift programming and I am trying to set a slider min, max and value in a NSToolbar. As an hypothetical exemple, I have a list of client and I want to use the slider in the toolbar to select a client data page. I will firt to load the client database in the NSViewController and count the number of client. Than I would like to set the slider in the toolbar minvalue to 1 and maxvalue to the number of client. I understand how to send slider values from the Windowcontroller to the ViewController but I did not found how to do the inverse , how to send data from the Viewcontroller to the Window controller in order to set the slider values.
I have attach an simple code based on this exemple https://github.com/gbdavid2/DavidCodes_macOS/tree/master/NSToolbar%20with%20Storyboards/NSToolbar%20with%20Storyboards
In this exemple, the Toolbar shows a Previous and an Next button that , when clicked, they change a counter value (count). I would like to send back that value from the ViewCoOntroller to the WindowController in order to display it in label and eventually, the slider value in the toolbar. Thanks for your help.
// WindowController.swift
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var myBoutton: NSToolbarItem!
var viewController: ViewController {
get {
return self.window!.contentViewController! as! ViewController
}
}
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
//viewController.myLabel.stringValue = "boo"
}
#IBAction func previous(_ sender: Any) {
viewController.updateMyLabelText(newText: "Prev Button clicked! ")
}
#IBAction func next(_ sender: Any) {
viewController.updateMyLabelText(newText: "Next Button clicked! ")
}
}
import Cocoa
class ViewController: NSViewController {
var count : Int = 0
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func updateMyLabelText(newText: String){
if newText.contains("Prev") {count -= 1}
else if newText.contains("Next") {count += 1}
myLabel.stringValue = newText + String(count)
}
}
Another way to to achieve this is with Cocoa Bindings. Example:
In the toolbar are a Previous button, a Next button and a slider. The actions of the buttons are connected to the First Responder. The action methods are implemented in ViewController. The count property of ViewController has attributes #objc dynamic so it can be used with Cocoa Bindings.
class ViewController: NSViewController {
#objc dynamic var count: Int = 0
#IBAction func previous(_ sender: Any) {
count -= 1
}
#IBAction func next(_ sender: Any) {
count += 1
}
}
The slider in the toolbar is bound to the Window Controller, key path window.contentViewController.count.
In the view is a label with a number formatter. The value of the label is bound to the View Controller, key path count.
The window controller isn't subclassed.
There are multiple ways to achieve this.
One of the way is by creating a class [e.g: SliderManager] which keep tracks of current value and handles increment/decrement. You can get the current value of Slider with the help of Singleton in any Controller.
Here is an example implementation:
protocol SliderCountDelegate: NSObject {
func counterDidUpdate()
}
final class SliderCountManager {
static let shared = SliderCountManager()
var value: UInt8 = 0 // <-- Unsigned Integers: Only Positive numbers
weak var delegate: SliderCountDelegate?
public func increaseCounter() {
value += 1
delegate?.counterDidUpdate()
}
public func decreaseCounter() {
value -= 1
delegate?.counterDidUpdate()
}
}
Here is how you should use this in your code:
// WindowController.swift
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var myBoutton: NSToolbarItem!
override func windowDidLoad() {
super.windowDidLoad()
}
#IBAction func previous(_ sender: Any) {
SliderCountManager.shared.increaseCounter()
print(SliderCountManager.shared.value) // <- Accessing Current value here
}
#IBAction func next(_ sender: Any) {
SliderCountManager.shared.decreaseCounter()
print(SliderCountManager.shared.value)
}
}
import Cocoa
class ViewController: NSViewController, SliderCountDelegate {
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
SliderCountManager.shared.delegate = self // Set Delegate to `self`
}
override var representedObject: Any? {
didSet {
}
}
// Protocol conformance
func counterDidUpdate() {
myLabel.stringValue = String(SliderCountManager.shared.value)
}
}
Thanks for the proposed solutions. It certainly put me in the wrigth direction.
Here is what I did. In the WindowController , I set a toolbar with 1) button «previous», 2) button «next» and 3) a slider «slider».
Those are linked to the proper IBOutler and IBaction in the WindowController.
The viewController have a textLabel «myLabel»
The 2 buttons and the slider change the slider_ptr value in the ViewControler and is sent to myLabel. Also, the slider.label change according to the slider_pointer and the slider_max values. Here is the code for the windowController:
import Cocoa
class WindowController: NSWindowController {
#IBOutlet weak var slider: NSSlider!
#IBOutlet weak var sliderTB: NSToolbarItem!
var viewController: ViewController {
get {
return self.window!.contentViewController! as! ViewController
}
}
override func windowDidLoad() {
super.windowDidLoad()
setSlider() // set initial value based on ViewController
}
#IBAction func previous(_ sender: Any) {
viewController.previous (WindowController())
setSlider()
}
#IBAction func next(_ sender: Any) {
//viewController.updateMyLabelText(newText: "Prev Button clicked! ")
viewController.next (WindowController()) //send to VC function previous
// let pt = viewController.slider_ptr + 1
//let sMax = viewController.slider_max
setSlider()
//sliderTB.label = String(pt) + " de " + String(sMax)
}
#IBAction func sliderDidChange(_ sender: Any) {
viewController.sliderDidSlide (WindowController(), pointer: Int(slider.doubleValue))
setSlider()
// viewController.sliderDidSlide(PosiWC(), sValue: myslider.doubleValue)
}
func setSlider() {
/* myslider.minValue = 1
myslider.maxValue = Double(max)
myslider.integerValue = pointer*/
//print ("WCP58:" , myslider.integerValue )
let pt = viewController.slider_ptr
let sMax = viewController.slider_max
//slider (max : pt, pointer: sMax)
sliderTB.label = String(pt) + " de " + String(sMax)
slider.minValue = 1
slider.maxValue = Double(sMax)
slider.integerValue = pt
}
}
and for the Viewcontroller :
class ViewController: NSViewController {
var slider_ptr = 1 // slider position
var slider_max: Int = 0 //
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
slider_max = 250
myLabel.stringValue = String(slider_ptr)
}
override var representedObject: Any? {
didSet {
}
}
func previous(_ sender: Any) {
if slider_ptr > 1 {
slider_ptr -= 1
}
else { NSSound.beep()}
myLabel.stringValue = String(slider_ptr)
}
func next(_ sender: Any) {
if slider_ptr < slider_max {
slider_ptr += 1
}
else { NSSound.beep()}
myLabel.stringValue = String(slider_ptr)
}
func sliderDidSlide(_ sender: Any, pointer : Int) {
print (pointer)
slider_ptr = pointer
myLabel.stringValue = String(slider_ptr)
}
}

How do I add a placeholder to my UITextField? (Using Xcode 12)

EDIT: New ErrorCurrently using XCode 12, and I'm trying to add a placeholder. I followed the Swift QuestionBot documentation but it doesn't work (I'm assuming it's because my XCode is much newer). Anyway, appreciate all the help!
EDIT: I added an image of a new error I got.
EDIT 2: Added MyQuestionAnswerer() struct! It's on a different view controller (obvious).
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var responseLabel: UILabel!
#IBOutlet weak var askButton: UIButton!
#IBOutlet weak var questionField: UITextField!
let questionAnswerer = MyQuestionAnswerer()
override func viewDidLoad() {
super.viewDidLoad()
questionField.becomeFirstResponder()
}
func respondToQuestion(_ question: String) {
let answer = questionAnswerer.responseTo(question: question)
displayAnswerTextOnScreen(answer)
questionField.placeholder = "Ask another question..."
questionField.text = nil
askButton.isEnabled = false
}
#IBAction func askButtonTapped(_ sender: AnyObject) {
guard questionField.text != nil else {
return
}
questionField.resignFirstResponder()
}
func displayAnswerTextOnScreen(_ answer: String) {
responseLabel.text = answer
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else {
return
}
respondToQuestion(text)
}
#IBAction func editingChanged(_ textField: UITextField) {
guard let text = textField.text else {
askButton.isEnabled = false
return
}
askButton.isEnabled = !text.isEmpty
}
}
struct MyQuestionAnswerer {
func responseTo(question: String) -> String {
let loweredQuestion = question.lowercased()
if loweredQuestion == "What is the current vaccination rate of the Philippines?" {
return "As of August 8, the vaccination rate of the Philippines is 10%!"
} else if loweredQuestion.hasPrefix("Where") {
return "Check the map for nearby vaccination centers."
}
}
The placeholder is not present when the textFiled as it is not set until the respondToQuestion method is called. It should probably be set inside of a view controller life cycle method such as viewDidLoad().
Example:
override func viewDidLoad() {
super.viewDidLoad()
questionField.placeholder = "Ask another question"
questionField.becomeFirstResponder()
}

How can I append text to the active UITextField - Swift

I'm trying to make a custom keyboard. But I cannot input info on the active textField. I'm not sure what I'm doing wrong.
PS: The keyboard is in another ViewController and is passing the sender.tag well.
Here is my code:
import UIKit
class HomeVC: UIViewController, ButtonTapDelegate, UITextFieldDelegate {
#IBOutlet var textField1: UITextField!
#IBOutlet var textField2: UITextField!
#IBOutlet var keyboardView: UIView!
var activeField: UITextField?
var delegate: ButtonTapDelegate!
override func viewDidLoad() {
addKeyboard(view: keyboardView)
textField1.inputView = UIView()
textField2.inputView = UIView()
textField1.becomeFirstResponder()
activeField?.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
activeField = textField
}
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
keyboard.delegate = self
view.addSubview(keyboard.view)
addChild(keyboard)
}
func didTapButton(sender: UIButton) {
if sender.tag == 8 {
activeField?.text?.append(contentsOf: " ")
} else if sender.tag == 9 {
activeField?.text?.removeAll()
} else {
let val = sender.titleLabel?.text
activeField?.text?.append(contentsOf: val!)
}
}
}
There is a slight problem in your code that is causing your issue. In the comments, you mentioned that didTapButton() is called beforetextFieldDidBeginEditing. This means that actionField is not assigned a value and therefore is nil. Your code in didTapButton() safely unwraps the optional value so that no error is produced, but of course, you cannot append text to a non-existent UITextField.
I was able to fix it after some research with changes to the textFieldDidBeginEditing and didTapButton functions: Here is the full code if anybody wants to choose one textField at a time with a custom keyboard:
import UIKit
class HomeVC: UIViewController, ButtonTapDelegate, UITextFieldDelegate {
#IBOutlet var textField1: UITextField!
#IBOutlet var textField2: UITextField!
#IBOutlet var keyboardView: UIView!
var activeField: UITextField?
var delegate: ButtonTapDelegate!
override func viewDidLoad() {
addKeyboard(view: keyboardView)
textField1.inputView = UIView()
textField2.inputView = UIView()
textField1.becomeFirstResponder()
activeField?.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.activeField = textField
}
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
keyboard.delegate = self
view.addSubview(keyboard.view)
addChild(keyboard)
}
func didTapButton(sender: UIButton) {
if textField1 == self.activeField {
if sender.tag == 8 {
textField1.text?.append(contentsOf: " ")
} else if sender.tag == 9 {
textField1.text?.removeAll()
} else {
let val = sender.titleLabel?.text?
textField1.text?.append(contentsOf: val!)
}
return;
}
if textField2 == self.activeField {
if sender.tag == 8 {
textField2.text?.append(contentsOf: " ")
} else if sender.tag == 9 {
textField2.text?.removeAll()
} else {
let val = sender.titleLabel?.text?
textField2.text?.append(contentsOf: val!)
}
return;
}
}
}

How do I dismiss keyboard with a touch? touchesBegan not workig

According to everything I've read here, I should override touchesBegan() to dismiss the keyboard (in my case DatePicker). Unfortunately touching the screen does not dismiss the DatePicker. Touching other UI elements like the UISteppers dismisses the keyboard just fine, and it is using the same function of CloseKeyboard()
class RecordWorkoutTableViewController: UITableViewController, UITextFieldDelegate {
#IBOutlet weak var dateTextField: UITextField!
#IBOutlet weak var weightLabel: UILabel!
#IBOutlet weak var setOneLabel: UILabel!
#IBOutlet weak var setTwoLabel: UILabel!
#IBOutlet weak var weightStepper: UIStepper!
#IBOutlet weak var setOneStepper: UIStepper!
#IBOutlet weak var setTwoStepper: UIStepper!
var newDate: NSDate? {
didSet {
dateTextField.text = NSDateToPrettyString(newDate!)
}
}
var newWeight: Int? {
didSet {
weightLabel.text = "\(newWeight!) lbs"
}
}
var newSetOne: Int? {
didSet {
setOneLabel.text = "Set 1: \(newSetOne!) reps"
}
}
var newSetTwo: Int? {
didSet {
setTwoLabel.text = "Set 2: \(newSetTwo!) reps"
}
}
var workout: Workout?
// MARK: UITextFieldDelegate
func textFieldDidBeginEditing(textField: UITextField) {
let datePicker = UIDatePicker()
textField.inputView = datePicker
datePicker.addTarget(self, action: #selector(RecordWorkoutTableViewController.datePickerChanged(_:)), forControlEvents: .ValueChanged)
}
func datePickerChanged(sender: UIDatePicker) {
newDate = sender.date
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
// disable editing of date text. datepicker input only
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return false
}
// MARK: Helper Functions
func closeKeyboard() {
self.view.endEditing(true)
}
// MARK: Touch Events
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
closeKeyboard()
}
override func viewDidLoad() {
super.viewDidLoad()
dateTextField.delegate = self
newDate = NSDate()
if let lastWorkout = workout {
newWeight = lastWorkout.sets[0].weight
newSetOne = lastWorkout.sets[0].repCount
newSetTwo = lastWorkout.sets[1].repCount
} else {
newWeight = 0
newSetOne = 9
newSetTwo = 8
}
weightStepper.stepValue = 5
weightStepper.maximumValue = 995
weightStepper.value = Double(newWeight!)
setOneStepper.stepValue = 1
setOneStepper.maximumValue = 20
setOneStepper.value = Double(newSetOne!)
setTwoStepper.stepValue = 1
setTwoStepper.maximumValue = 20
setTwoStepper.value = Double(newSetTwo!)
}
#IBAction func weightStepperChanged(sender: UIStepper) {
newWeight = Int(sender.value)
closeKeyboard()
}
#IBAction func setOneStepperChanged(sender: UIStepper) {
newSetOne = Int(sender.value)
closeKeyboard()
}
#IBAction func setTwoStepperChanged(sender: UIStepper) {
newSetTwo = Int(sender.value)
closeKeyboard()
}
}
You said in the comments that you have another class in your app named Set. Since this class is in the same module as your table view controller, Swift is prioritizing it over it's built in class.
You can fix this by either renaming your Set class, or explicitly specifying the Swift module in the function declaration:
override func touchesBegan(touches: Swift.Set<UITouch>, withEvent event: UIEvent?) {
closeKeyboard()
}

My textFields doesn't disappear when is invoked viewWillAppear method in swift

import UIKit
import Alamofire
import SystemConfiguration
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var simpleLabel: UILabel!
#IBOutlet weak var uiNameSearch: UITextField!
#IBOutlet weak var uiGivenName: UITextField!
var patient1 = Patient!()
override func viewDidLoad() {
super.viewDidLoad()
uiNameSearch.delegate = self
uiGivenName.delegate = self
print("ViewController viewDidLoad")
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.simpleLabel.center.x -= self.view.bounds.width
self.uiGivenName.center.x -= self.view.bounds.width
print("Call viewWillAppear")
}
override func viewDidAppear(animated: Bool){
super.viewDidAppear(animated)
UIView.animateWithDuration(0.9 , animations: {
// self.simpleLabel.center.x += self.view.bounds.width
self.uiGivenName.center.x += self.view.bounds.width
})
UIView.animateWithDuration(0.7 , delay: 0.7, options: [], animations: {
self.uiNameSearch.center.x += self.view.bounds.width
}, completion: nil )
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func searchForName(sender: UIButton) {
let header = ["Accept" : "application/json"]
let name = uiNameSearch.text!
let given = uiGivenName.text!
if name.characters.count == 0 {
self.view.makeToast( message: "Please insert the family name of the patient!")
} else if given.characters.count == 0 {
self.view.makeToast(message: "Please insert the patient given name!")
} else if checkInternetConnection() == false {
self.view.makeToast(message: "Please connect to the internet!")
} else {
Alamofire.request(.GET, "https://open-ic.epic.com/FHIR/api/FHIR/DSTU2/Patient?family=\(name)&given=\(given)", headers: header).responseJSON { response in
self.patient1 = Patient(response: response.result.value!)
self.performSegueWithIdentifier("showSegue", sender: sender)
}
}
}
func checkInternetConnection() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
switch textField {
case uiNameSearch:
uiNameSearch.resignFirstResponder()
uiGivenName.becomeFirstResponder()
case uiGivenName:
uiGivenName.resignFirstResponder()
default:
print("")
}
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject?){
if segue.identifier == "showSegue" {
if let displayViewController = segue.destinationViewController as? DisplayViewController {
displayViewController.patient1 = patient1
}
}
}
}
I want to make uiGivenName and simpleLabel to disappear until the view is created and after when the viewDidAppear is invoked to appear from the left side.
Your view doesn't disappear because you are changing position of views in viewWillAppear and when viewWillAppear gets called, view is about to be added to view hierarchy(i.e view is still not added to view hierarchy.), so its not reflecting in your UI.
So you can do your stuff in viewDidLayoutSubviews because this is the best place if you want to modify your UI just before it actually appears in the screen.
Edit -
Replace this -
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.simpleLabel.center.x -= self.view.bounds.width
self.uiGivenName.center.x -= self.view.bounds.width
print("Call viewWillAppear")
}
with
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.simpleLabel.center.x -= self.view.bounds.width
self.uiGivenName.center.x -= self.view.bounds.width
}