Reputation: 557
I have made a subclass of NSControl
which is a combination of a number formatted NSTextField
and an NSStepper
. Here is the basic code:
class StepperWithNumberField: NSControl, NSTextFieldDelegate {
let numberField: NSTextField
let stepper: NSStepper
override var intValue: Int32 {
get {
return stepper.intValue
}
set {
stepper.intValue = newValue
numberField.intValue = newValue
}
}
init(frame: NSRect, numberFieldWidth: CGFloat, minValue: Int32, maxValue: Int32) {
let numberFieldFrame = NSRect(x: 0, y: 0, width: numberFieldWidth, height: frame.height)
let numberField = NSTextField(frame: numberFieldFrame)
numberField.isEditable = true
numberField.alignment = .right
let formatter = NumberFormatter()
formatter.allowsFloats = false
formatter.minimum = minValue as NSNumber
formatter.maximum = maxValue as NSNumber
numberField.formatter = formatter
self.numberField = numberField
let stepper = NSStepper(frame: NSRect(x: numberFieldWidth, y: 0, width: 15, height: frame.height))
stepper.minValue = Double(minValue)
stepper.maxValue = Double(maxValue)
stepper.increment = 1
stepper.valueWraps = false
self.stepper = stepper
super.init(frame: frame)
self.numberField.delegate = self
self.stepper.target = self
self.stepper.action = #selector(self.stepperClicked)
self.addSubview(numberField)
self.addSubview(stepper)
intValue = minValue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func controlTextDidEndEditing(_ notification: Notification) {
if let nobj = notification.object as? NSTextField {
stepper.intValue = nobj.intValue
}
}
@objc func stepperClicked(sender: NSStepper) {
numberField.intValue = stepper.intValue
}
}
If I create an instance and add it as a subview to my main view this works as expected: I can click the stepper buttons to change the value and I can enter a number directly.
However, I do not know how I can define the action that would inform my target (the superview) of any changes.
I create an instance like that:
let stepper = StepperWithNumberField(....)
stepper.target = self
stepper.action = #selector(self.stepperChanged)
and define the method like that:
@objc func stepperChanged(sender: StepperWithNumberField) {
...
}
But that method is never called, as the StepperWithNumberField does not know when any action is to be sent to the target.
I want the action to be sent to the target whenever the intValue has changed, either from using the stepper buttons or from direct user input.
Where - if even possible - do I tell the class the conditions for sending the action? I have searched Apple's documentation as well as this community but most of the questions and sample code about that topic are more than 10 years old and do not deal with swift.
In this thread...
NSControl with multiple actions
...it is suggested to use delegation, which - of course - would be a solution, but I still am interested in how it would work to implement the target-action pattern in a custom NSControl.
Upvotes: 1
Views: 252
Reputation: 557
Inspired by Leo Chen's answer I did a little more experimentation and came up with the following: Simply add:
self.sendAction(action, to: target)
to the stepperClicked
and controlTextDidEndEditing
methods of my StepperWithNumberField
class.
Usage then:
let stepper = StepperWithNumberField(...)
stepper.target = self
stepper.action = #selector(self.stepperChanged)
@objc func stepperChanged(sender: StepperWithNumberField) {
}
Upvotes: 0
Reputation: 340
public typealias Handler = (StepperWithNumberField) -> Void
private var handler: Handler?
@objc func stepperClicked(sender: NSStepper) {
numberField.intValue = stepper.intValue
self.handler?(self)
}
use
let stepper = StepperWithNumberField(....)
stepper.target = self
stepper.handler = stepperChanged(sender:)
func stepperChanged(sender: StepperWithNumberField) {
}
Upvotes: 2