Reputation: 391
I have the following view:
struct SpriteView: View {
@Binding var name: String
@State var sprite: Image = Image(systemName: "exclamationmark")
var body: some View {
VStack{
sprite
}
.onAppear(perform: loadSprite)
}
func loadSprite() {
// async function
getSpriteFromNetwork(self.name){ result in
switch result {
// async callback
case .success(newSprite):
self.sprite = newSprite
}
}
}
What I want to happen is pretty simple: a user modifies name
in text field (from parent view), which reloads SpriteView
with the new sprite. But the above view doesn't work since when the view is reloaded with the new name, loadSprite
isn't called again (onAppear
only fires when the view is first loaded). I also can't put loadSprite
in the view itself (and have it return an image) since it'll lead to an infinite loop.
There is a beta function onChange
that is exactly what I'm looking for, but it's only in the beta version of Xcode. Since Combine is all about async callbacks and SwiftUI and Combine are supposed to play well together, I thought this sort of behavior would be trivial to implement but I've been having a lot of trouble with it.
Upvotes: 0
Views: 90
Reputation: 391
I don't particular like this solution since it requires creating a new ObservableObject
but this how I ended up doing it:
class SpriteLoader: ObservableObject {
@Published var sprite: Image = Image(systemName: "exclamationmark")
func loadSprite(name: String) {
// async function
self.sprite = Image(systemName: "arrow.right")
}
}
struct ParentView: View {
@State var name: String
@State var spriteLoader = SpriteLoader()
var body: some View {
SpriteView(spriteLoader: spriteLoader)
TextField(name, text: $name, onCommit: {
spriteLoader.loadSprite(name: name)
})
}
}
struct SpriteView: View {
@ObservedObject var spriteLoader: SpriteLoader
var body: some View {
VStack{
spriteLoader.sprite
}
}
}
Old answer:
I think the best way to do this is as follows:
Parent view:
struct ParentView: View {
@State var name: String
@State spriteView = SpriteView()
var body: some View {
spriteView
TextField(value: $name, onCommit: {
spriteView.loadSprite(name)
})
}
And then the sprite view won't even need the @Binding name
member.
Upvotes: 0