Last active
October 27, 2022 15:21
-
-
Save colejd/b92cfdda95ad4103eb01ee926cba7140 to your computer and use it in GitHub Desktop.
View+Sync.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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