使用 RxSwift 自定义 UIControl 子类
Custom UIControl subclass with RxSwift
我正在创建 UIControl 的自定义子类(我需要重写它的绘制方法)并且我想添加 RxSwift 以将其 isSelected 属性 绑定到我的模型.
到目前为止一切顺利。这很好用。
我的问题是如何更改值 isSelected 属性 以响应用户 touchUpInside 事件?
我的第一次尝试是使用 UIControl 的 addTarget 方法,但是 ControlProperty 没有报告以编程方式更改 isSelected 的值(如文档中所述)。但我可以想出另一种方法来解决这个问题。
感谢任何帮助。
子类的源代码:
class SYYesNoButton: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
// subscribe to touchUpInside event
addTarget(
self,
action: #selector(userDidTouchUpInside),
for: UIControlEvents.touchUpInside)
}
func userDidTouchUpInside() {
// change the value of the property
// this does not work,
// the change is not reported to the ControlProperty
// HOW CAN I CHANGE THIS ??
self.isSelected = !isSelected
}
}
添加响应式支持的扩展:
extension SYYesNoButton {
var rx_isSelected: ControlProperty<Bool> {
return UIControl.valuePublic(
self,
getter: { (button) -> Bool in
return button.isSelected
},
setter: { (button, value) in
button.isSelected = value
})
}
}
extension UIControl {
static func valuePublic<T, ControlType: UIControl>(_ control: ControlType, getter: @escaping (ControlType) -> T, setter: @escaping (ControlType, T) -> ()) -> ControlProperty<T> {
let values: Observable<T> = Observable.deferred { [weak control] in
guard let existingSelf = control else {
return Observable.empty()
}
return (existingSelf as UIControl).rx.controlEvent([.allEditingEvents, .valueChanged])
.flatMap { _ in
return control.map { Observable.just(getter([=11=])) } ?? Observable.empty()
}
.startWith(getter(existingSelf))
}
return ControlProperty(values: values, valueSink: UIBindingObserver(UIElement: control) { control, value in
setter(control, value)
})
}
}
谢谢大家。
如果您是从 UIControl 子class,那么您正在制作自己的控件 class 并且您必须覆盖 beginTracking(_:with:)
、continueTracking(_:with:)
中的一个或多个、endTracking(_:with:)
或 cancelTracking(with:)
以使控件按您希望的方式工作。然后用正确的事件调用 sendActions(for:)
。 UIControl 的内部不会有 Rx。
从 UIButton 获取队列,您的控件本身不应 select,尽管它可以突出显示和取消突出显示(例如,当用户的手指放在上面时。)
一旦你正确地创建了你的 UIControl,代码外部控件可以使用 Rx 来观察它,你不需要额外的工作。
以下作品(为 Swift 5/RxSwift 5 更新):
class ViewController: UIViewController {
@IBOutlet weak var yesNoButton: SYYesNoButton!
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
yesNoButton.rx.controlEvent(.touchUpInside)
.scan(false) { v, _ in !v }
.bind(to: yesNoButton.rx.isSelected)
.disposed(by: bag)
}
}
@IBDesignable
class SYYesNoButton: UIControl {
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = isSelected ? .green : .red
}
override var isSelected: Bool {
didSet {
super.isSelected = isSelected
backgroundColor = isSelected ? .green : .red
}
}
}
一旦你有了一个实际的 UIControl,有一个更好的方法来 "native" RxCocoa 扩展,称为 ControlProperty
使用 RxCocoa 中的辅助方法。
例如:
extension Reactive where Base: someControl {
var someProperty: ControlProperty<Float> {
return controlProperty(editingEvents: .valueChanged,
getter: { [=10=].value },
setter: { [=10=].value = })
}
}
每当触发指定的 UIControlEvent
时,这将公开 getter
块中的当前值,并且还会在某些流绑定到它时设置该值。
它有点像 Observable 和 Observer 类型的组合——您可以观察它的值,但也可以订阅它。
我正在创建 UIControl 的自定义子类(我需要重写它的绘制方法)并且我想添加 RxSwift 以将其 isSelected 属性 绑定到我的模型.
到目前为止一切顺利。这很好用。
我的问题是如何更改值 isSelected 属性 以响应用户 touchUpInside 事件?
我的第一次尝试是使用 UIControl 的 addTarget 方法,但是 ControlProperty 没有报告以编程方式更改 isSelected 的值(如文档中所述)。但我可以想出另一种方法来解决这个问题。
感谢任何帮助。
子类的源代码:
class SYYesNoButton: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
// subscribe to touchUpInside event
addTarget(
self,
action: #selector(userDidTouchUpInside),
for: UIControlEvents.touchUpInside)
}
func userDidTouchUpInside() {
// change the value of the property
// this does not work,
// the change is not reported to the ControlProperty
// HOW CAN I CHANGE THIS ??
self.isSelected = !isSelected
}
}
添加响应式支持的扩展:
extension SYYesNoButton {
var rx_isSelected: ControlProperty<Bool> {
return UIControl.valuePublic(
self,
getter: { (button) -> Bool in
return button.isSelected
},
setter: { (button, value) in
button.isSelected = value
})
}
}
extension UIControl {
static func valuePublic<T, ControlType: UIControl>(_ control: ControlType, getter: @escaping (ControlType) -> T, setter: @escaping (ControlType, T) -> ()) -> ControlProperty<T> {
let values: Observable<T> = Observable.deferred { [weak control] in
guard let existingSelf = control else {
return Observable.empty()
}
return (existingSelf as UIControl).rx.controlEvent([.allEditingEvents, .valueChanged])
.flatMap { _ in
return control.map { Observable.just(getter([=11=])) } ?? Observable.empty()
}
.startWith(getter(existingSelf))
}
return ControlProperty(values: values, valueSink: UIBindingObserver(UIElement: control) { control, value in
setter(control, value)
})
}
}
谢谢大家。
如果您是从 UIControl 子class,那么您正在制作自己的控件 class 并且您必须覆盖 beginTracking(_:with:)
、continueTracking(_:with:)
中的一个或多个、endTracking(_:with:)
或 cancelTracking(with:)
以使控件按您希望的方式工作。然后用正确的事件调用 sendActions(for:)
。 UIControl 的内部不会有 Rx。
从 UIButton 获取队列,您的控件本身不应 select,尽管它可以突出显示和取消突出显示(例如,当用户的手指放在上面时。)
一旦你正确地创建了你的 UIControl,代码外部控件可以使用 Rx 来观察它,你不需要额外的工作。
以下作品(为 Swift 5/RxSwift 5 更新):
class ViewController: UIViewController {
@IBOutlet weak var yesNoButton: SYYesNoButton!
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
yesNoButton.rx.controlEvent(.touchUpInside)
.scan(false) { v, _ in !v }
.bind(to: yesNoButton.rx.isSelected)
.disposed(by: bag)
}
}
@IBDesignable
class SYYesNoButton: UIControl {
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = isSelected ? .green : .red
}
override var isSelected: Bool {
didSet {
super.isSelected = isSelected
backgroundColor = isSelected ? .green : .red
}
}
}
一旦你有了一个实际的 UIControl,有一个更好的方法来 "native" RxCocoa 扩展,称为 ControlProperty
使用 RxCocoa 中的辅助方法。
例如:
extension Reactive where Base: someControl {
var someProperty: ControlProperty<Float> {
return controlProperty(editingEvents: .valueChanged,
getter: { [=10=].value },
setter: { [=10=].value = })
}
}
每当触发指定的 UIControlEvent
时,这将公开 getter
块中的当前值,并且还会在某些流绑定到它时设置该值。
它有点像 Observable 和 Observer 类型的组合——您可以观察它的值,但也可以订阅它。