Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fooling around with SwiftUI and The Elm Architecture
import Combine
import SwiftUI
final class Store<Model, Message>: BindableObject {
private(set) var model: Model {
willSet { willChange.send() }
}
let update: (inout Model, Message) -> Command<Message>?
let willChange = PassthroughSubject<Void, Never>()
init(
model: Model,
command: Command<Message>? = nil,
update: @escaping (inout Model, Message) -> Command<Message>?
) {
self.model = model
self.update = update
if let command = command {
execute(command)
}
}
func send(_ message: Message) {
if let command = update(&model, message) {
execute(command)
}
}
private func execute(_ command: Command<Message>) {
command.execute { message in
DispatchQueue.main.async {
self.send(message)
}
}
}
}
struct Command<Message> {
let execute: (@escaping (Message) -> Void) -> Void
}
extension Command where Message == AppMessage {
static var roll: Command {
Command { callback in
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
callback(.newFace(Int.random(in: 1 ... 6)))
}
}
}
}
struct AppModel {
var dieFace: Int? = nil
}
enum AppMessage {
case roll
case newFace(Int)
}
func update(
model: inout AppModel,
message: AppMessage
) -> Command<AppMessage>? {
switch message {
case .roll:
model.dieFace = nil
return .roll
case .newFace(let face):
model.dieFace = face
return .none
}
}
struct Root: View {
@ObjectBinding var store: Store<AppModel, AppMessage>
var body: some View {
VStack {
Text(store.model.dieFace.map(String.init) ?? "Rolling...")
Button(action: { self.store.send(.roll) }) {
Text("Roll")
}
.disabled(store.model.dieFace == nil)
}
}
}
let store = Store(
model: AppModel(),
command: .roll,
update: update
)
import PlaygroundSupport
PlaygroundPage.current.liveView = UIHostingController(
rootView: Root(store: store)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.