gpt4 book ai didi

swift - 从标准 View 构建的自定义 View 的目标操作问题

转载 作者:行者123 更新时间:2023-11-28 07:46:16 24 4
gpt4 key购买 nike

我有一个自定义 View 子类 NSView,它只是一个包含标签、 slider 、第二个标签和复选框的 NSStackView。 slider 和复选框都配置为报告对 View 的更改(并最终通过委托(delegate)给 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。切换复选框或更改 slider 将产生所需的效果。

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

顺便说一下,@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)
}
}

最佳答案

如 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(_:)))

关于swift - 从标准 View 构建的自定义 View 的目标操作问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50948337/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com