从标准视图构建的自定义视图的目标操作问题

时间:2018-06-20 12:30:43

标签: swift macos

我有一个自定义视图子类NSView,它只是一个NSStackView,其中包含标签,滑块,第二个标签和一个复选框。滑块和复选框都配置为向视图报告更改(并最终通过委托给ViewController):

fileprivate extension NSTextField {
    static func label(text: String? = nil) -> NSTextField {
        let label = NSTextField()
        label.isEditable = false
        label.isSelectable = false
        label.isBezeled = false
        label.drawsBackground = false
        label.stringValue = text ?? ""
        return label
    }
}

@IBDesignable
class Adjustable: NSView {

    private let sliderLabel = NSTextField.label()
    private let slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
    private let valueLabel = NSTextField.label()
    private let enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))

    var valueFormatter: (Double)->(String) = { String(format:"%5.2f", $0) }

    ...

    @objc func sliderChanged(_ sender: Any) {
        guard let slider = sender as? NSSlider else { return }

        valueLabel.stringValue = valueFormatter(slider.doubleValue)
        print("Slider now: \(slider.doubleValue)")

        delegate?.adjustable(self, changedValue: slider.doubleValue)
    }

    @objc func enabledChanged(_ sender: Any) {
        guard let checkbox = sender as? NSButton else { return }
        print("Enabled now: \(checkbox.state == .on)")

        delegate?.adjustable(self, changedEnabled: checkbox.state == .on)
    }
}

使用InterfaceBuilder,我可以通过拖入CustomView并在Identity Inspector中将其设置为类来将其一个实例添加到ViewController。切换复选框或更改滑块将具有所需的效果。

但是,如果我有多个实例,那么在目标动作函数中,self将始终引用视图的同一实例,而不是与之交互的视图。换句话说,self.slider == sender仅在sliderChanged中对其中一个滑块有效。虽然我可以通过sender获得正确的滑块值,但是由于self.valueLabel始终是自定义视图的第一个实例中的标签,因此我无法更新正确的标签。

顺便说一句,@IBDesignable和旨在支持它的代码没有任何作用,因此我也缺少一些东西-Interface Builder仅显示空白。

整个文件:

import Cocoa

fileprivate extension NSTextField {
    static func label(text: String? = nil) -> NSTextField {
        let label = NSTextField()
        label.isEditable = false
        label.isSelectable = false
        label.isBezeled = false
        label.drawsBackground = false
        label.stringValue = text ?? ""
        return label
    }
}

protocol AdjustableDelegate {
    func adjustable(_ adjustable: Adjustable, changedEnabled: Bool)
    func adjustable(_ adjustable: Adjustable, changedValue: Double)
}

@IBDesignable
class Adjustable: NSView {
    var delegate: AdjustableDelegate? = nil

    private let sliderLabel = NSTextField.label()
    private let slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
    private let valueLabel = NSTextField.label()
    private let enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))

    var valueFormatter: (Double)->(String) = { String(format:"%5.2f", $0) }

    @IBInspectable
    var label: String = "" {
        didSet {
            sliderLabel.stringValue = label
        }
    }

    @IBInspectable
    var value: Double = 0 {
        didSet {
            slider.doubleValue = value
            valueLabel.stringValue = valueFormatter(value)
        }
    }

    @IBInspectable
    var enabled: Bool = false {
        didSet {
            enabledCheckbox.isEnabled = enabled
        }
    }

    @IBInspectable
    var minimum: Double = 0 {
        didSet {
            slider.minValue = minimum
        }
    }

    @IBInspectable
    var maximum: Double = 100 {
        didSet {
            slider.maxValue = maximum
        }
    }

    @IBInspectable
    var tickMarks: Int = 0


    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
        setup()
    }

    override func prepareForInterfaceBuilder() {
        setup()
    }

    override func awakeFromNib() {
        setup()
    }

    private func setup() {
        let stack = NSStackView()
        stack.orientation = .horizontal
        stack.translatesAutoresizingMaskIntoConstraints = false

        stack.addArrangedSubview(sliderLabel)
        stack.addArrangedSubview(slider)
        stack.addArrangedSubview(valueLabel)
        stack.addArrangedSubview(enabledCheckbox)

        sliderLabel.stringValue = label
        slider.doubleValue = value
        valueLabel.stringValue = valueFormatter(value)
        slider.minValue = minimum
        slider.maxValue = maximum
        slider.numberOfTickMarks = tickMarks

        // Make the slider be the one that expands to fill available space
        slider.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 249), for: .horizontal)

        sliderLabel.widthAnchor.constraint(equalToConstant: 60).isActive = true
        valueLabel.widthAnchor.constraint(equalToConstant: 60).isActive = true

        addSubview(stack)

        stack.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        stack.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        stack.topAnchor.constraint(equalTo: topAnchor).isActive = true
        stack.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }

    @objc func sliderChanged(_ sender: Any) {
        guard let slider = sender as? NSSlider else { return }

        valueLabel.stringValue = valueFormatter(slider.doubleValue)
        print("Slider now: \(slider.doubleValue)")

        delegate?.adjustable(self, changedValue: slider.doubleValue)
    }

    @objc func enabledChanged(_ sender: Any) {
        guard let checkbox = sender as? NSButton else { return }
        print("Enabled now: \(checkbox.state == .on)")

        delegate?.adjustable(self, changedEnabled: checkbox.state == .on)
    }
}

1 个答案:

答案 0 :(得分:0)

如Willeke链接的问题所述,解决方案是确保在引用init之前完成self。 (令我惊讶的是编译器允许它在属性初始化程序中使用)

错误:

private let slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
private let enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))

右:

private lazy var slider = NSSlider(target: self, action: #selector(sliderChanged(_:)))
private lazy var enabledCheckbox = NSButton(checkboxWithTitle: "Enabled", target: self, action: #selector(enabledChanged(_:)))