CombineReactor is a ReactorKit inspired wrapper for Swift Combine.
Includes a "Binder" to add bindable capabilities to default implementation by conforming to ReactiveCompatible and extending Reactive. See the example below.
Contributions and feedback are welcome and appreciated.
import Foundation
import Combine
import CombineReactor
class CounterReactor: Reactor {
enum Action {
case decrease
case increase
}
enum Mutation {
case decreaseValue
case increaseValue
case setIsLoading(Bool)
}
struct State {
var value: Int = 0
var isLoading: Bool = false
}
let initialState = State()
func mutate(action: Action) -> AnyPublisher<Mutation, Never> {
switch action {
case .decrease:
let step: AnyPublisher<Mutation, Never> = Empty()
.append(.decreaseValue)
.delay(for: .seconds(1), scheduler: RunLoop.current)
.eraseToAnyPublisher()
return Empty()
.append(.setIsLoading(true))
.append(step)
.append(.setIsLoading(false))
.eraseToAnyPublisher()
case .increase:
let step: AnyPublisher<Mutation, Never> = Empty()
.append(.increaseValue)
.delay(for: .seconds(1), scheduler: RunLoop.current)
.eraseToAnyPublisher()
return Empty()
.append(.setIsLoading(true))
.append(step)
.append(.setIsLoading(false))
.eraseToAnyPublisher()
}
}
func reduce(state: State, mutation: Mutation) -> State {
var newState = state
switch mutation {
case .decreaseValue:
newState.value -= 1
case .increaseValue:
newState.value += 1
case .setIsLoading(let isLoading):
newState.isLoading = isLoading
}
return newState
}
}extension UILabel: ReactiveCompatible {}
extension Reactive where Base: UILabel {
var text: Binder<String?> {
Binder(base) { (label, text) in
label.text = text
}
}
}
extension UIActivityIndicatorView: ReactiveCompatible {}
extension Reactive where Base: UIActivityIndicatorView {
var isAnimating: Binder<Bool> {
Binder(base) { (activityIndicator, isAnimating) in
isAnimating ? activityIndicator.startAnimating() : activityIndicator.stopAnimating()
}
}
}extension ViewController: View {
func bind(reactor: CounterReactor) {
buttonActionPassthrough
.eraseToAnyPublisher()
.sink(receiveValue: reactor.action.send)
.store(in: &cancellables)
reactor.state
.map { $0.value }
.map { String(format: "%i", $0) }
.bind(to: valueLabel.r.text )
.store(in: &cancellables)
reactor.state
.map { $0.isLoading }
.removeDuplicates()
.bind(to: activityIndicator.r.isAnimating)
.store(in: &cancellables)
}
}pod 'CombineReactor', git: 'https://github.com/ilendemli/CombineReactor.git'.package(url: "https://github.com/ilendemli/CombineReactor.git", .upToNextMajor(from: "0.1"))Feel free to create a pull request
- Swift 5.1
- iOS 13
- watchOS 6
- tvOS 13
- macOS 10.15
CombineReactor is available under the MIT license. See the LICENSE file for more info.