2

I'm trying to create isHighlighted Observable for my UIButton, to send sequence every time isHiglighted for UIButton has changed. And I've write something like this

extension Reactive where Base: UIButton {

    var isHighlighted: Observable<Bool> {

        let property = self.base.rx.controlProperty(editingEvents: .allTouchEvents,
                                                    getter: { _ in self.base.isHighlighted },
                                                    setter: { (_, _) in })
        return property
            .distinctUntilChanged()
            .asObservable()
    }
}

The problem is, that it doesn't work for .touchUpInside. If I drag finger outside UIButton and then come back, it works fine, but not for tap action. I think immediately after .touchUpInside it's still highlighted for very short time.

Michal Rogowski
  • 431
  • 3
  • 18
  • I hoped to find the Apple doc that specifically addresses this, but failed. I found what sets the button state to `highlighted` - *"For example, when the user taps a button with a title, the button moves to the highlighted state."* - but not what sets it to... `default`? But it makes sense (at least to me) that *until* something else receives "focus" (to use a MSFT term) theOS might be designed to keep the "focus" on the button. Thus, the "delay" on removing the highlighted state. –  Feb 02 '18 at 23:08
  • @dfd Do you have any other idea how to create an Observable for isHighlighted UIButton property ? Without subclassing UIButton – Michal Rogowski Feb 02 '18 at 23:27
  • I don't work with [rx-swift], but could you create a simple extension to `UIButton` and override `isHightlighted`, doing what you need in `didSet`. I just tried this and is built with no errors. –  Feb 02 '18 at 23:32

2 Answers2

7

Thanks @iWheelBuy I've created code that works, without RxOptional, I based it partially on your answer so Thank you! Here is working code:

extension Reactive where Base: UIButton {
    var isHighlighted: Observable<Bool> {
        let anyObservable = self.base.rx.methodInvoked(#selector(setter: self.base.isHighlighted))

        let boolObservable = anyObservable
            .flatMap { Observable.from(optional: $0.first as? Bool) }
            .startWith(self.base.isHighlighted)
            .distinctUntilChanged()
            .share()

        return boolObservable
    }
}
Michal Rogowski
  • 431
  • 3
  • 18
1

I think I have a solution. It can be simplified, but I just copy paste the full solution I have.

public extension Reactive where Base: UIButton {

    public func isHighlighted() -> Observable<Bool> {
        let selector = #selector(setter: UIButton.isHighlighted)
        let value: ([Any]) -> Bool? = { $0.first(where: { $0 is Bool }) as? Bool }
        return base
            .observable(selector: selector, value: value)
            .filterNil()
            .startWith(base.isHighlighted)
            .distinctUntilChanged()
            .share(replay: 1, scope: .whileConnected)
    }
}

Also, to make it work. You need RxOptional and some extra code:

public enum InvocationTime: Int {

    case afterMessageIsInvoked
    case beforeMessageIsInvoked
}

and

public extension NSObject {

    public func observable<T>(selector: Selector, value: @escaping ([Any]) -> T, when: InvocationTime = .afterMessageIsInvoked) -> Observable<T> {
        let observable: Observable<[Any]> = {
            switch when {
            case .afterMessageIsInvoked:
                return rx.methodInvoked(selector)
            case .beforeMessageIsInvoked:
                return rx.sentMessage(selector)
            }
        }()
        return observable
            .map({ value($0) })
            .share(replay: 1, scope: .whileConnected)
    }
}

Hope it helps (^

iWheelBuy
  • 5,470
  • 2
  • 37
  • 71