我有一个自定义视图子类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)
}
}
答案 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(_:)))