Reactivecocoa: rac_textSignal for UISearchBar

Created on 13 Jan 2016  路  7Comments  路  Source: ReactiveCocoa/ReactiveCocoa

AFAIR in RAC 2.x there was a rac_textSignal category on UISearchBar or at least you could get the current text in the search bar by rac_signalForSelector.

I am trying to get text signal from UISearchBar in RAC 4.0 by doing:

    let searchString = searchBar.rac_signalForSelector(Selector("searchBar:textDidChange:"), fromProtocol: UISearchBarDelegate.self)
    searchString.subscribeNext { (a:AnyObject!) -> Void in
      let s = a as! String
      print("Searching for: \(s)")
    }

but my subscription is never executed. @Adlai-Holler mentions in #2498 that selectors will be gone in 4.0. How do you then get text from UISearchBar in RAC 4.0?

legacy-objc question

Most helpful comment

Hi guys, what do you think about my code below, is there any downside or maybe even something wrong?

extension UISearchBar {
  var rac_text: MutableProperty<String?> {
    return lazyAssociatedProperty(self, key: &AssociationKey.text) {
      self.delegate = self.delegateProxy
      let property = MutableProperty<String?>(self.text)
      property <~ self.delegateProxy.signal
      return property
    }
  }

  private var delegateProxy: UISearchBarDelegateProxy {
    return lazyAssociatedProperty(self, key: &AssociationKey.delegate) {
      return UISearchBarDelegateProxy(pipe: Signal<String?, NoError>.pipe())
    }
  }

  private class UISearchBarDelegateProxy: NSObject, UISearchBarDelegate {
    let signal: Signal<String?, NoError>
    let observer: Observer<String?, NoError>

    init(pipe: (signal: Signal<String?, NoError>, observer: Observer<String?, NoError>)) {
      signal = pipe.signal
      observer = pipe.observer
    }

    @objc private func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
      observer.sendNext(searchBar.text)
    }
  }
}

struct AssociationKey {
  static var text: UInt8 = 0
  static var delegate: UInt8 = 1
}

func lazyAssociatedProperty<T: AnyObject>(host: AnyObject, key: UnsafePointer<Void>, factory: ()->T) -> T {
  return objc_getAssociatedObject(host, key) as? T ?? {
    let associatedProperty = factory()
    objc_setAssociatedObject(host, key, associatedProperty, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    return associatedProperty
    }()
}

All 7 comments

The way I do this is to make an associatedObject of the signal and subscribe the delegate to itself.
This approach wil claim the delegate, so if you need it for something else you will need to proxy it.
It is not an ideal solution but it works.

extension UISearchBar : UISearchBarDelegate {
    public var rac_searchBarTextDidChange : Signal<(UISearchBar,String?),NoError> {
        var searchBarTextDidChange: UInt8 = 0

        var value = objc_getAssociatedObject(self, &searchBarTextDidChange) as? Signal<(UISearchBar,String?),NoError>
        if value == nil {
            self.delegate = self

            value = self.rac_signalForSelector(Selector("searchBar:textDidChange:"), fromProtocol: UISearchBarDelegate.self)
                .asSignal()
                .ignoreError()
                .map { ($0 as? RACTuple)?.first as? UISearchBar }
                .ignoreNil()
                .map { ($0, $0.text) }

            objc_setAssociatedObject(self,  &searchBarTextDidChange, value, .OBJC_ASSOCIATION_RETAIN)
        }
        return value!
    }
}

@avalanched this would actually be a great fit for Rex!

true, but I think we would benefit more from a proxy delegate pattern instead of claiming the delegate for a single purpose

rac_signalForSelector:fromProtocol: should be called on a delegatee (which conforms to the protocol), not on a delegator (searchBar in this case).

let searchString = self.rac_signalForSelector(Selector("searchBar:textDidChange:"), fromProtocol: UISearchBarDelegate.self)
searchBar.delegate = self
searchString.subscribeNext { ... }

Hi guys, what do you think about my code below, is there any downside or maybe even something wrong?

extension UISearchBar {
  var rac_text: MutableProperty<String?> {
    return lazyAssociatedProperty(self, key: &AssociationKey.text) {
      self.delegate = self.delegateProxy
      let property = MutableProperty<String?>(self.text)
      property <~ self.delegateProxy.signal
      return property
    }
  }

  private var delegateProxy: UISearchBarDelegateProxy {
    return lazyAssociatedProperty(self, key: &AssociationKey.delegate) {
      return UISearchBarDelegateProxy(pipe: Signal<String?, NoError>.pipe())
    }
  }

  private class UISearchBarDelegateProxy: NSObject, UISearchBarDelegate {
    let signal: Signal<String?, NoError>
    let observer: Observer<String?, NoError>

    init(pipe: (signal: Signal<String?, NoError>, observer: Observer<String?, NoError>)) {
      signal = pipe.signal
      observer = pipe.observer
    }

    @objc private func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
      observer.sendNext(searchBar.text)
    }
  }
}

struct AssociationKey {
  static var text: UInt8 = 0
  static var delegate: UInt8 = 1
}

func lazyAssociatedProperty<T: AnyObject>(host: AnyObject, key: UnsafePointer<Void>, factory: ()->T) -> T {
  return objc_getAssociatedObject(host, key) as? T ?? {
    let associatedProperty = factory()
    objc_setAssociatedObject(host, key, associatedProperty, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    return associatedProperty
    }()
}

its not "wrong", but for the single purpose of getting a stream of text changes it will add much more complexity than the proposal of @ikesyo stated above.

But isn't rac_signalForSelector:fromProtocol something that you guys plan to drop?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

danishin picture danishin  路  4Comments

toddbluhm picture toddbluhm  路  5Comments

samidalouche picture samidalouche  路  6Comments

BrettThePark picture BrettThePark  路  4Comments

RuiAAPeres picture RuiAAPeres  路  3Comments