Skip to content

Instantly share code, notes, and snippets.

@colejd
Last active October 27, 2022 15:21
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 colejd/b92cfdda95ad4103eb01ee926cba7140 to your computer and use it in GitHub Desktop.
Save colejd/b92cfdda95ad4103eb01ee926cba7140 to your computer and use it in GitHub Desktop.
View+Sync.swift
extension View {
/**
From here: https://www.youtube.com/watch?v=3a7tuhVpoTQ
More discussion here: https://stackoverflow.com/a/73590189
This method synchronizes two bindings so that updates to one
result in updates to the other.
**Why do this?**
Have you ever gotten this error? `Publishing changes from within view updates is not allowed, this will cause undefined behavior.`
As of SwiftUI 4, you are not supposed to use bindings to `@Published`
variables (I have yet to see a definitive source on this, and you may
see this behavior change - I see this behavior as of XCode 14.1).
To get around this, you'll need to declare a local `@State` var where
you would normally create a binding, and then use this method to
synchronize the `@State` and `@Published` variable.
For example, this is now wrong to do:
```
class ViewManager: ObservableObject {
@Published var showSheet: Bool = false
}
struct SomeView: View {
@StateObject private var viewManager = ViewManager()
var body: some View {
Text("Hello world!")
.sheet(isPresented: $viewManager.showSheet)
}
}
```
Here's the fixed version using this method:
```
class ViewManager: ObservableObject {
@Published var showSheet: Bool = false
}
struct SomeView: View {
@StateObject private var viewManager = ViewManager()
@State private var showSheet: Bool = false
var body: some View {
Text("Hello world!")
.sheet(isPresented: $showSheet)
.sync($viewManager.showSheet, with: $showSheet)
}
}
```
This is most helpful when you're passing an ObservableObject with published
variables as an @EnvironmentObject. Note that you only need to do this
where the binding is used to drive View state! So in `SomeView` above, you need
this sort of shadowing, but if you have some other view that needs to modify
the published value, you don't need to sync - just update it like normal.
*/
func sync<T: Equatable>(_ published: Binding<T>, with binding: Binding<T>) -> some View {
self
.onChange(of: published.wrappedValue) { published in
binding.wrappedValue = published
}
.onChange(of: binding.wrappedValue) { binding in
published.wrappedValue = binding
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment