Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,46 @@ class ReactiveGestureReactor: GestureReactor {
private let timerCreator: ReactiveTimerCreator
private let disposeBag = DisposeBag()

private let panVariable: Variable<UIGestureRecognizerType?>
private let rotateVariable: Variable<UIGestureRecognizerType?>
private let panVariable: Variable<UIGestureRecognizerType>
private let rotateVariable: Variable<UIGestureRecognizerType>

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<UIGestureRecognizerState> in
return Observable.just(gesture!.state)
}

let panGesturesStartedEnded = panVariable.asObservable().filter { gesture in gesture?.state == .Began || gesture?.state == .Ended}.flatMap { (gesture) -> Observable<UIGestureRecognizerState> 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<UIGestureRecognizerState> 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
Expand All @@ -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)

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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


///
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down