diff --git a/functional-reactive-intuition/Project/RFP/ReactiveGestureReactor.swift b/functional-reactive-intuition/Project/RFP/ReactiveGestureReactor.swift index 2e243db..dd9d62d 100644 --- a/functional-reactive-intuition/Project/RFP/ReactiveGestureReactor.swift +++ b/functional-reactive-intuition/Project/RFP/ReactiveGestureReactor.swift @@ -11,53 +11,46 @@ class ReactiveGestureReactor: GestureReactor { private let timerCreator: ReactiveTimerCreator private let disposeBag = DisposeBag() - private let panVariable: Variable - private let rotateVariable: Variable + private let panVariable: Variable + private let rotateVariable: Variable - init(timerCreator: ReactiveTimerCreator) { + init(timerCreator: ReactiveTimerCreator, gestureRecognizers: (UIGestureRecognizerType, UIGestureRecognizerType)) { self.timerCreator = timerCreator - panVariable = Variable(nil) - rotateVariable = Variable(nil) - + self.panVariable = Variable(gestureRecognizers.0) + self.rotateVariable = Variable(gestureRecognizers.1) // FYI // Passing on the UIGesture at this point is dodgy as it's a reference // It's state will change and render our filter useless. // We therefore keep just the state in our observable buffers [.Began,.Began,.Ended] - let rotateGesturesStartedEnded = rotateVariable.asObservable().filter { gesture in gesture?.state == .Began || gesture?.state == .Ended}.flatMap { (gesture) -> Observable in - return Observable.just(gesture!.state) - } - let panGesturesStartedEnded = panVariable.asObservable().filter { gesture in gesture?.state == .Began || gesture?.state == .Ended}.flatMap { (gesture) -> Observable in - return Observable.just(gesture!.state) - } + let rotateGesturesStartedEnded = rotateVariable.asObservable() + .map { $0.state } + .filter { $0 == .Began || $0 == .Ended } + + let panGesturesStartedEnded = panVariable.asObservable() + .map { $0.state } + .filter { $0 == .Began || $0 == .Ended } // Combine our latest .Began and .Ended from both Pan and Rotate. - // If they are the same then return the same state. If not then return a Failed. - let combineStartEndGestures = Observable.combineLatest(panGesturesStartedEnded, rotateGesturesStartedEnded) { (panState, rotateState) -> Observable in - - // If only one is .Ended, the result is .Ended too - var state = UIGestureRecognizerState.Ended - if panState == .Began && rotateState == .Began { - state = .Began + // If they are the same then return the same state. If not then return .Ended. + let combinedGesture = Observable + .combineLatest(rotateGesturesStartedEnded, panGesturesStartedEnded) { ($0, $1) } + .map { ($0.0 == .Began && $0.1 == .Began) + ? UIGestureRecognizerState.Began + : UIGestureRecognizerState.Ended } - - return Observable.just(state) - }.switchLatest() - - // several .Began events in a row are to be treated the same as a single one, it has just meaning if a .Ended is in between - let distinceCombineStartEndGestures = combineStartEndGestures.distinctUntilChanged() - + .map { state -> UIGestureRecognizerState in + print(state) + return state } + .distinctUntilChanged() + // several .Began events in a row are to be treated the same as a single one, it has just meaning if a .Ended is in between // condition: when both pan and rotate has begun - let bothGesturesStarted = distinceCombineStartEndGestures.filter { (state) -> Bool in - state == .Began - } + let bothGesturesStarted = combinedGesture.filter { $0 == .Began } // condition: when both pan and rotate has Ended - let bothGesturesEnded = distinceCombineStartEndGestures.filter { (state) -> Bool in - state == .Ended - } + let bothGesturesEnded = combinedGesture.filter { $0 == .Ended } // when bothGesturesStarted, do this: bothGesturesStarted.subscribeNext { [unowned self] _ in @@ -70,18 +63,16 @@ class ReactiveGestureReactor: GestureReactor { // condition: and also, stop it immediately when both pan and rotate ended let timerThatTicksThreeAndStops = timerThatTicksThree.takeUntil(bothGesturesEnded) - timerThatTicksThreeAndStops.subscribe(onNext: { [unowned self] count in - // the imperative version waits for a second until didComplete is called, so we have to tick once more, but do not send the last tick to the delegate - guard count < 4 else { - return - //do nothing - } - // when a tick happens, do this: - self.delegate?.didTick(3 - count) + timerThatTicksThreeAndStops + // the imperative version waits for a second until didComplete is called, so we have to tick once more, but do not send the last tick to the delegate + .filter { $0 < 3 } + .subscribe(onNext: { [unowned self] count in + // when a tick happens, do this: + self.delegate?.didTick(2 - count) }, onCompleted: { [unowned self] in // when the timer completes, do this: self.delegate?.didComplete() - }) + }) }.addDisposableTo(self.disposeBag) } diff --git a/functional-reactive-intuition/Project/RFP/ReactiveViewController.swift b/functional-reactive-intuition/Project/RFP/ReactiveViewController.swift index 73b53a0..2eec32e 100644 --- a/functional-reactive-intuition/Project/RFP/ReactiveViewController.swift +++ b/functional-reactive-intuition/Project/RFP/ReactiveViewController.swift @@ -17,21 +17,22 @@ class ReactiveViewController: UIViewController, SetStatus, GestureReactorDelegat @IBOutlet weak var centerXConstraint: NSLayoutConstraint! //For updating the position of the box when dragging @IBOutlet weak var centerYConstraint: NSLayoutConstraint! - private var gestureReactor: GestureReactor = ReactiveGestureReactor(timerCreator: { interval in ReactiveTimerFactory.reactiveTimer(interval: interval) }) + private var gestureReactor: GestureReactor! private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() - gestureReactor.delegate = self - let pan = UIPanGestureRecognizer(target: self, action: "handlePan:") pan.delegate = self let rotate = UIRotationGestureRecognizer(target: self, action: "handleRotate:") rotate.delegate = self - self.draggableView.gestureRecognizers = [pan, rotate] + draggableView.gestureRecognizers = [pan, rotate] + gestureReactor = ReactiveGestureReactor(timerCreator: { interval in ReactiveTimerFactory.reactiveTimer(interval: interval) }, gestureRecognizers: (pan, rotate)) + gestureReactor.delegate = self + /// /// diff --git a/functional-reactive-intuition/Project/RFPTests/ReactiveGestureReactorTests.swift b/functional-reactive-intuition/Project/RFPTests/ReactiveGestureReactorTests.swift index 9cd736f..dc4332c 100644 --- a/functional-reactive-intuition/Project/RFPTests/ReactiveGestureReactorTests.swift +++ b/functional-reactive-intuition/Project/RFPTests/ReactiveGestureReactorTests.swift @@ -19,7 +19,7 @@ class ReactiveGestureReactorTests: XCTestCase { self.mockTimer = mockTimer return mockTimer.asObservable().skip(1) } - sut = ReactiveGestureReactor(timerCreator: timerCreator) + sut = ReactiveGestureReactor(timerCreator: timerCreator, gestureRecognizers: (MockPanGestureRecognizer(state: .Cancelled), MockRotateGestureRecognizer(state: .Cancelled))) mockDelegate = MockGestureReactorDelegate() sut.delegate = mockDelegate }