Skip to content

Instantly share code, notes, and snippets.

@danielt1263
Created November 19, 2021 23:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielt1263/42080cc1f5eebb37f8eb904d2c787358 to your computer and use it in GitHub Desktop.
Save danielt1263/42080cc1f5eebb37f8eb904d2c787358 to your computer and use it in GitHub Desktop.
This gist contains a couple of examples of using the new cycle functionality using my CLE architecture.
import Cause_Logic_Effect
import RxCocoa
import RxSwift
import RxSwiftExt
import UIKit
final class ViewController: UIViewController {
var tableView: UITableView!
var logoutButton: UIButton!
let disposeBag = DisposeBag()
override func loadView() {
super.loadView()
logoutButton = UIButton(type: .system).setup {
$0.setTitle("Logout", for: .normal)
$0.sizeToFit()
}
var headerFrame = view.bounds
headerFrame.size.height = 44
let headerView = UIView(frame: headerFrame).setup {
$0.addSubview(logoutButton)
}
tableView = UITableView(frame: view.bounds).setup {
$0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
$0.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
$0.tableHeaderView = headerView
}
view.addSubview(tableView)
}
}
extension ViewController {
func connect() {
let api = API()
let token = cycle(
input: logoutButton.rx.tap.map(to: TokenLogic.Input.logout),
initialState: "",
reduce: TokenLogic.reducer,
reaction: reaction(
request: TokenLogic.showLoginScreen,
effect: { request in
request
.flatMapFirst(presentScene(animated: true) {
UIAlertController(title: "Login", message: "Enter your login info:", preferredStyle: .alert)
.scene { $0.connectLogin().map { TokenLogic.Input.login($0) } }
})
}
)
)
.share()
let pages = cycle(
inputs: PageLogic.inputs(
reachedBottom: tableView.rx.reachedBottom().asObservable(),
token: token
),
initialState: PageLogic.State(),
reduce: PageLogic.reducer,
reaction: reaction(
request: PageLogic.requestInput,
effect: { request in
request.flatMapLatest { request in
api.response(.passenger(page: request.page, size: 20, token: request.token))
.map { PageLogic.Input.response(page: request.page, response: $0) }
}
})
)
token
.map { $0.isEmpty ? "Login" : "Logout" }
.bind(to: logoutButton.rx.title(for: .normal))
.disposed(by: disposeBag)
Observable.merge(
pages.map { output in
output.pages.sorted { $0.key < $1.key }
.flatMap { $0.value }
},
token.map(to: [])
)
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { _, item, cell in
cell.textLabel?.text = item.name
}
.disposed(by: disposeBag)
}
}
enum TokenLogic {
enum Input {
case logout
case login(Credentials)
}
static func reducer(state: inout String, input: Input) {
switch input {
case .logout:
state = ""
case let .login(credentials):
state = credentials.password
}
}
static func showLoginScreen(state: String, input: Input) -> Bool {
guard case .logout = input, state == "" else { return false }
return true
}
}
enum PageLogic {
enum Input {
case updateToken(String)
case getPage
case response(page: Int, response: PassengersResponse)
}
struct State {
var pages: [Int: [Passenger]] = [:]
var token: String = ""
}
struct RequestInput {
let page: Int
let token: String
}
static func inputs(reachedBottom: Observable<Void>, token: Observable<String>) -> [Observable<Input>] {
[
reachedBottom.map(to: Input.getPage),
token.map(Input.updateToken)
]
}
static func reducer(state: inout State, input: Input) {
switch input {
case let .updateToken(token):
state.pages = [:]
state.token = token
case .getPage:
break
case .response(page: let page, response: let response):
state.pages[page] = response.data
}
}
static func requestInput(state: State, input: Input) -> RequestInput? {
switch input {
case let .updateToken(token):
guard !token.isEmpty else { return nil }
return RequestInput(page: 1, token: token)
case .getPage:
guard !state.token.isEmpty else { return nil }
return RequestInput(page: (state.pages.keys.sorted().last ?? 0) + 1, token: state.token)
case .response:
return nil
}
}
}
extension UIAlertController {
func connectLogin() -> Observable<Credentials> {
let credentials = PublishSubject<Credentials>()
let ok = UIAlertAction(title: "OK", style: .default, handler: { [unowned self] _ in
let username = self.textFields![0].text ?? ""
let password = self.textFields![0].text ?? ""
credentials.onSuccess(Credentials(username: username, password: password))
})
let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { _ in
credentials.onCompleted()
})
addAction(cancel)
addAction(ok)
addTextField { (textField) in
textField.placeholder = "Username"
}
addTextField { (textField) in
textField.placeholder = "Password"
}
_ = Observable.combineLatest(textFields!.map { $0.rx.text.orEmpty })
.map { $0.allSatisfy { !$0.isEmpty } }
.bind(to: ok.rx.isEnabled)
return credentials
}
}
extension Endpoint where Response == PassengersResponse {
static func passenger(page: Int, size: Int, token: String) -> Endpoint {
assert(!token.isEmpty)
var urlComponents = URLComponents(string: "https://api.instantwebtools.net/v1/passenger")!
urlComponents.queryItems = [
URLQueryItem(name: "page", value: "\(page)"),
URLQueryItem(name: "size", value: "\(size)")
]
var request = URLRequest(url: urlComponents.url!)
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
return Endpoint(request: request, decoder: JSONDecoder())
}
}
struct Credentials {
let username: String
let password: String
}
struct PassengersResponse: Decodable, Equatable {
let data: [Passenger]
}
struct Passenger: Decodable, Identifiable, Equatable {
let id: Identifier<String, Passenger>
let name: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case name
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment