Home > other >  How do I define an action for a custom NSControl in code?
How do I define an action for a custom NSControl in code?

Time:01-21

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.

CodePudding user response:

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) {
    
}

CodePudding user response:

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) {

} 
  •  Tags:  
  • Related