Rxswift: Required Delegate

Created on 16 Dec 2015  路  14Comments  路  Source: ReactiveX/RxSwift

_Hello_
I am trying to overload some delegate of a custom classe to a Oversable Object.
Some delegate are required.

For example with the SearchBar example, I should implement the required delegate definition in the * RxSearchBarDelegateProxy* ? I tried to follow the UITableViewDataSource example

class RxSearchBarDelegateProxy : DelegateProxy
                               , UISearchBarDelegate
                               , DelegateProxyType {

    class func currentDelegateFor(object: AnyObject) -> AnyObject? {
        let searchBar: UISearchBar = castOrFatalError(object)
        return searchBar.delegate
    }

    class func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
        let searchBar: UISearchBar = castOrFatalError(object)
        searchBar.delegate = castOptionalOrFatalError(delegate)
    }
}
extension UISearchBar {

    public var rx_delegate: DelegateProxy {
        return proxyForObject(RxSearchBarDelegateProxy.self, self)
    }

    public var rx_text: Observable<String> {
        return defer { [weak self] in
            let text = self?.text ?? ""

            return self?.rx_delegate.observe("searchBar:textDidChange:") ?? empty()
                    .map { a in
                        return a[1] as? String ?? ""
                    }
                    .startWith(text)
        }
    }
}

I have also some issue like :

Delegate proxy is already implementing `methodRequired:`, a more performant way of registering might exist.

Thanks in advance.

Most helpful comment

@VinayakKkini don鈥檛 (just) forward calls to self._forwardToDelegate - it doesn鈥檛 do what you think it does. You need to implement a PublishSubject on your DelegateProxy and send it a .Next in the delegate method. I鈥檝e done a write-up on the GIDSignIn example on my blog.

All 14 comments

Hi @remirobert ,

please take a look at RxScrollViewDelegateProxy and use of

internal var contentOffsetSubject: Observable<CGPoint> {
public func scrollViewDidScroll(scrollView: UIScrollView) {
        if let contentOffset = _contentOffsetSubject {
            contentOffset.on(.Next(scrollView.contentOffset))
        }
        self._forwardToDelegate?.scrollViewDidScroll?(scrollView)
    }

... that should hopefully give you an idea how to wrap this.

That warning should be printed in case we detect that an explicit implementation of method exist on delegate proxy, and implementor can just use a normal subject mechanism to wrap that method (we would simulate that during automagic "swizzling observing" method anyway).

We could also add some additional swizzling code that observes those calls, but since somebody is already wrapping that method, adding additional subject isn't a huge effort, and this is simpler and cleaner approach :)

This looks stale. Closing this.

@kzaher
Stuck with the same issue. I'm trying to enable Rx support for this ImagePicker.
My implementation looks like:

class RxImagePickerDelegareProxy: DelegateProxy, ImagePickerDelegate, DelegateProxyType {

  static func currentDelegateFor(object: AnyObject) -> AnyObject? {
    let picker: ImagePickerController = object as! ImagePickerController
    return picker.delegate
  }

  static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
    let picker: ImagePickerController = object as! ImagePickerController
    picker.delegate = delegate as? ImagePickerDelegate
  }

  func wrapperDidPress(images: [UIImage]) {
    self._forwardToDelegate?.wrapperDidPress?(images)
  }

  func doneButtonDidPress(images: [UIImage]) {
    self._forwardToDelegate?.doneButtonDidPress?(images)
  }

  func cancelButtonDidPress() {
    self._forwardToDelegate?.cancelButtonDidPress?()
  }
}

extension ImagePickerController {

  public var rx_delegate: DelegateProxy {
    return proxyForObject(RxImagePickerDelegareProxy.self, self)
  }

  public var rx_wrapperDidPress: Observable<[UIImage]> {
    return rx_delegate.observe("wrapperDidPress:")
    .map { params in
      return params[0] as! [UIImage]
    }
  }

  public var rx_doneButtonDidPress: Observable<[UIImage]> {
    return rx_delegate.observe("doneButtonDidPress:")
      .map { params in
        return params[0] as! [UIImage]
    }
  }

  public var rx_cancelButtonDidPress: Observable<Bool> {
    return rx_delegate.observe("cancelButtonDidPress")
      .map { _ in return true }
  }
}

But the output is:

Delegate proxy is already implementing `doneButtonDidPress:`, a more performant way of registering might exist.
Delegate proxy is already implementing `cancelButtonDidPress`, a more performant way of registering might exist.

The subscription code (don't work):

    imagePicker.rx_doneButtonDidPress.subscribeNext { [weak self] (images) -> Void in
        self?.manager.memoryStorage.addItems(images)
        self?.updatePhotosState()
        self?.dismissViewControllerAnimated(true, completion: nil)

      }.addDisposableTo(disposeBag)

    imagePicker.rx_cancelButtonDidPress.subscribeNext { [weak self] _ -> Void in
      self?.dismissViewControllerAnimated(true, completion: nil)
    }.addDisposableTo(disposeBag)

@orkenstein - I鈥檝e just hit this as well, and managed to resolve it eventually. It could be a lot clearer how to do this to be honest - the relevant parts of that UIScrollView example are sprayed across several files.

When you implement required delegate methods in your proxy, rx_delegate.observe() will not receive any events for that method. It鈥檚 a bit confusing because that鈥檚 what it _looks_ like self._forwardToDelegate is doing, but I think all that does is forward to a traditional delegate if one is set.

To implement this, you鈥檒l generally want to declare a PublishSubject in your proxy, and manually forward events to it, e.g.:

let doneButtonPressedSubject = PublishSubject<[UIImage]>()

func doneButtonDidPress(images: [UIImage]) {
    doneButtonPressedSubject.on(.Next(images))
    self._forwardToDelegate?.doneButtonDidPress?(images)
}

Then in your ImagePickerController extension, you just want to return the PublishSubject instead of calling rx_delegate.observe().

public var rx_doneButtonDidPress: Observable<[UIImage]> {
    let proxy = proxyForObject(RxImagePickerDelegateProxy.self, self)
    return proxy.doneButtonPressedSubject
}

Hope this helps.

@samritchie
Thanks for reply.
That's strange but I get this:
Value of type 'RxImagePickerDelegateProxy' has no member 'doneButtonPressedSubject'

Hmm, that is odd. You're definitely declaring doneButtonPressedSubject In the proxy? Maybe check for typos?

@samritchie, yep 100%. have no idea why. Maybe you can check the whole part?
https://gist.github.com/orkenstein/a4303673979d13f7b939fb3aae596c6f

@orkenstein looks like I called the type RxImagePickerDelegateProxy and you had RxImagePickerDelegareProxy

@samritchie
Oh thanks! The same old story.

Stuck with the same issue. My implementations is as follows.

class RxGoogleLoginDelegateProxy: DelegateProxy,GIDSignInDelegate,GIDSignInUIDelegate,DelegateProxyType {
    //We need a way to read the current delegate 
    static func currentDelegateFor(object: AnyObject) -> AnyObject? {
        let googleSignIn: GIDSignIn = object as! GIDSignIn
        return googleSignIn.delegate
    }

    //We need a way to set the current delegate
    static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
        let googleSignIn: GIDSignIn = object as! GIDSignIn
        googleSignIn.delegate = delegate as? GIDSignInDelegate
    }

    func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!, withError error: NSError!) {
        self._forwardToDelegate?.signIn(signIn, didSignInForUser: user, withError: error)
    }

    func signIn(signIn: GIDSignIn!, didDisconnectWithUser user: GIDGoogleUser!, withError error: NSError!) {
        self._forwardToDelegate?.signIn(signIn, didSignInForUser: user, withError: error)
    }
}
extension GIDSignIn {

    public var rx_delegate: DelegateProxy {
        return RxGoogleLoginDelegateProxy.proxyForObject(self)
    }

    public var rx_userDidSignIn: Observable<(GIDSignIn!,GIDGoogleUser!,NSError!)> {
        return rx_delegate.observe(#selector(GIDSignInDelegate.signIn(_:didSignInForUser:withError:)))
            .map { params in
                return (params[0] as! GIDSignIn,params[1] as? GIDGoogleUser,params[2] as? NSError)
        }
    }

    public var rx_userDidDisconnect: Observable<(GIDSignIn,GIDGoogleUser?,NSError?)> {
        return rx_delegate.observe(#selector(GIDSignInDelegate.signIn(_:didDisconnectWithUser:withError:))).map({ params in
            return (params[0] as! GIDSignIn,params[1] as? GIDGoogleUser,params[2] as? NSError)

        })

    }

}
public class GoogleAuthProvider : LoginAuthenticator {
    let disposeBag = DisposeBag()

    public func login(fromController controller: UIViewController) -> Observable<AnyObject> {
        return googleLogin()
    }

    public func signUp(fromController controller: UIViewController) -> Observable<AnyObject> {
        return googleLogin()
    }

    func googleLogin() -> Observable<AnyObject> {
        return Observable.create ({
            (Observer) -> Disposable in
            GIDSignIn.sharedInstance().rx_userDidSignIn.subscribeNext({ (signIn,user,error) in
                if (error != nil) {
                    if let user = user {
                        Observer.onNext(user)
                        Observer.onCompleted()
                    }
                } else {
                    if let error = error {
                        Observer.onError(error)
                    }
                }

            }).addDisposableTo(self.disposeBag)

            GIDSignIn.sharedInstance().rx_userDidDisconnect.subscribeNext({ (signIn,user,error) in
                if (error != nil) {
                    if let user = user {
                        Observer.onNext(user)
                        Observer.onCompleted()
                    }
                } else {
                    if let error = error {
                        Observer.onError(error)
                    }
                }
            }).addDisposableTo(self.disposeBag)

            GIDSignIn.sharedInstance().signIn()

            return AnonymousDisposable({})
        })
    }

}

I see these in the logs
_Delegate proxy is already implementing signIn:didSignInForUser:withError:, a more performant way of registering might exist.
Delegate proxy is already implementing signIn:didDisconnectWithUser:withError:, a more performant way of registering might exist._

Also Subscription doesn't seem to work. Can anyone help me here.

@VinayakKkini don鈥檛 (just) forward calls to self._forwardToDelegate - it doesn鈥檛 do what you think it does. You need to implement a PublishSubject on your DelegateProxy and send it a .Next in the delegate method. I鈥檝e done a write-up on the GIDSignIn example on my blog.

@samritchie Thanks a lot. This fixed it.

@samritchie How did you wrap-up UI Delegate in there?

@gabrieloliva GIDUIDelegate doesn鈥檛 have any required methods, so it鈥檚 a more conventional delegate proxy implementation. Use proxyForObject(), and wrap/map an observe call to provide better naming/typing. See https://gist.github.com/sakiwei/2a5633f8fa45cdaf9285695bb82922ff for a good example.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

acecilia picture acecilia  路  3Comments

RobinFalko picture RobinFalko  路  3Comments

tyregor picture tyregor  路  3Comments

RafaelPlantard picture RafaelPlantard  路  3Comments

retsohuang picture retsohuang  路  3Comments