Skip to content

Instantly share code, notes, and snippets.

@tclementdev
Last active May 11, 2023 12:11
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tclementdev/c402bf5fddd8570287874333bc45a474 to your computer and use it in GitHub Desktop.
Save tclementdev/c402bf5fddd8570287874333bc45a474 to your computer and use it in GitHub Desktop.
import SwiftUI
// This is an example of how to keep the ObservableObjects near the root of the view hierarchy
// and dispatch actions from any view easily without having to pass too many things down. This
// helps keep the views simple and makes previews a lot easier.
//
// This is inspired from what Apple is doing with `DismissAction`, `OpenURLAction`, `OpenWindowAction`, etc...
// The following is a very simple example with only two view-levels and we could pass closures directly,
// but this gets really useful when dealing with complex view hierarchies that have many view levels.
struct ContentView: View {
@StateObject var controller = Controller()
var body: some View {
SubView()
.actions(controller.actions)
}
}
struct SubView: View {
@Environment(\.actions) var actions
var body: some View {
Button("create") {
let document = actions.create()
print("\(document.name)")
}
Button("send") {
Task {
let document = Document(name: "doc")
try await actions.send(document)
}
}
}
}
struct Document {
let name: String
}
final class Controller: ObservableObject {
var actions: Actions {
Actions(create: create, send: send(_:))
}
func create() -> Document {
// create some document
return Document(name: "doc")
}
func send(_ document: Document) async throws {
// send document here
}
}
struct Actions {
let create: () -> Document
let send: (Document) async throws -> Void
}
struct ActionsKey: EnvironmentKey {
static let defaultValue = Actions(create: { fatalError() }, send: { _ in fatalError() })
}
extension EnvironmentValues {
var actions: Actions {
get { self[ActionsKey.self] }
set { self[ActionsKey.self] = newValue }
}
}
extension View {
func actions(_ actions: Actions) -> some View {
environment(\.actions, actions)
}
}
@helje5
Copy link

helje5 commented May 10, 2023

That's what I'd do instead, w/o an artificial extra struct:

struct ContentView: View {
    @StateObject var controller = Controller()
    
    var body: some View {
        SubView()
            .actions(controller)
    }
}

struct SubView: View {
    @Environment(\.actions) var actions
    
    var body: some View {
        Button("create") {
            let document = actions.create()
            print("\(document.name)")
        }
        Button("send") {
            Task {
                let document = Document(name: "doc")
                try await actions.send(document)
            }
        }
    }
}

struct Document {
    let name: String
}

final class Controller: ObservableObject, Actions {
    
    func create() -> Document {
        // create some document
        Document(name: "doc")
    }
    func send(_ document: Document) async throws {
        // send document here
    }
}

protocol Actions {
    func create() -> Document
    func send(_: Document) async throws -> Void
}

struct ActionsKey: EnvironmentKey {
    struct MIA: Actions {
      func create() -> Document { fatalError() }
      func send(_: Document) async throws -> Void { fatalError() }
    }
    static var defaultValue : Actions = MIA()
}

extension EnvironmentValues {
    var actions: Actions {
        get { self[ActionsKey.self] }
        set { self[ActionsKey.self] = newValue }
    }
}

extension View {
    func actions(_ actions: Actions) -> some View {
        environment(\.actions, actions)
    }
}

@tclementdev
Copy link
Author

tclementdev commented May 10, 2023

@helje5 yes I think this would work too. It would prevent forming actions from different objects, the struct is a bit more flexible, but in most situations it work probably just as well.

@tclementdev
Copy link
Author

@helje5 actually, calling fatalError() for the default value of the environment key is not good, this causes SwiftUI previews to crash. I cannot use that.

@helje5
Copy link

helje5 commented May 11, 2023

Adjusted.

@tclementdev
Copy link
Author

yep, the two pieces are almost the same now :p

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment