2

Struggling to get a simple ReactiveCocoa 4 example working.

  • I have pan gesture recogniser for a view in my hierarchy.
  • I have an intended destination class for my touch events (lets assume I want to generate network packets based on the touch position).

So it seems like a I want to create a signal from my gesture recogniser, map to extract the touch position relative to some view, then have my destination class observe this signal (or just have some final subscribeNext block that calls a method on my destination class).

However, can't seem to get anything working nor find a good example to follow.

I think I should be writing something like this (psuedo-code)

panRecognizer
    .rac_gestureSignal()
    .map { (pgr:UIPanGestureRecognizer) -> CGPoint in
        return pgr.locationInView(self.someUiView)
    }.subscribeNext { (location: CGPoint) -> Void in
        self.someNetworkDelegate.updatePosition(location)
    }

Is such a thing possible (it seems simple enough)? Am I perhaps trying to use the framework in a bad way?

fpg1503
  • 7,492
  • 6
  • 29
  • 49
chris838
  • 5,148
  • 6
  • 23
  • 27
  • This answer suggests to leverage RAC 2 extensions for that: http://stackoverflow.com/a/34169581/2128900. I don't know if there is a way to do that using RAC 4 only, though (maybe a 3rd party extension exists)? – Michał Ciuba Jan 10 '16 at 10:55
  • @MichałCiuba yep I saw that, doesn't compile in RAC 4. – chris838 Jan 10 '16 at 18:39

2 Answers2

3

You're actually really close there.

But you can't have RACSignals of CGPoints, because CGPoint is not a reference type. Generally, in the ReactiveCocoa 2 world, you get around this by explicitly boxing and unboxing types like NSValue and doing a fair bit of casting around:

panRecognizer.rac_gestureSignal()
    .map({ [unowned self] (pgr: AnyObject!) -> NSValue! in
        let pgr = pgr as! UIPanGestureRecognizer
        return NSValue(CGPoint: pgr.locationInView(self.someUiView))
    }).subscribeNext({[unowned self] locationValue in
        let location = locationValue.CGPointValue
        self.someNetworkDelegate.updatePosition(location)
    })

However, by converting the RACSignal into a SignalProducer, you can use the nice types you want. Doing this is... cumbersome, so I use the following helpers:

extension SignalProducer {
    func castSignal<T>() -> SignalProducer<T, Error> {
        return self.map({ $0 as! T })
    }

    func cantError() -> SignalProducer<Value, NoError> {
        // If we inline this, Swift will try to implicitly return the fatalError
        // expression. If we put an explicit return to stop this, it complains
        // it can never execute. One is an error, the other a warning, so we're
        // taking the coward's way out and doing this to sidestep the problem.
        func swallowError(error: Error) -> SignalProducer<Value, NoError> {
            fatalError("Underlying signal errored! \(error)")
        }

        return self.flatMapError(swallowError)
    }
}

extension RACSignal {
    func cast<T>() -> SignalProducer<T, NoError> {
        return self.toSignalProducer().cantError().map({ $0! }).castSignal()
    }
}

Obviously you'd need different helpers if you're converting signals that can error, but this works nicely for this example.

With those, you can write your code just like this:

    self.reportTapRecognizer.rac_gestureSignal().cast()
        .map({ [unowned self] (pgr: UIPanGestureRecognizer) -> CGPoint in
            return pgr.locationInView(self.someUiView)
        }).startWithNext({ [unowned self] location in
            self.someNetworkDelegate.updatePosition(location)
        })

Which is basically how you've written it above. Note that the type annotations are required in the map call so that cast can infer its return type properly.

Ian Henry
  • 22,255
  • 4
  • 50
  • 61
0

With RAC4 this one should work well..

let gesRec = UIPanGestureRecognizer(target: self, action: "gesRecHandler:")

    view.addGestureRecognizer(gesRec)
    gesRec.rac_gestureSignal().toSignalProducer().map({ (x) -> CGPoint in
        guard let pan = x as? UIPanGestureRecognizer else {return CGPointZero}
        return pan.locationInView(self.view)
    }).startWithNext { (pointInView) -> () in
        print(pointInView)
    }

In RAC4 there are some changes related to signals. So you need first of all convert your RACSignal to signal producer and start to observe it. You can read about signal producers in FrameworkOverview . Here is article about converting RACSignals to SignalProducer RACSignalToSignalProducer

Roman Derkach
  • 195
  • 13