简体   繁体   中英

SwiftUI is it possible to update ObservedObject in the current view without redrawing current view?

I have an ObservableObject called DataSource shared among some views. Each view has a button that changes a state variable to switch to the next view. In View2, I am trying to update some data in DataSource when button is pressed, which should be done right before it switches to the next view.

However when the button in View2 is pressed, even though the view is already switched to View3, View2 is also being redrawn in the background, and code in View2 body was executed again in the background (See output below, the list was printed twice), which is not what I want here.

Is there a way to modify the ObservableObject in the current view (eg, when pressing the button in current view) without redrawing the current view (or rerunning code in current view body)? Eg, when the button is pressed the current view should update the ObservableObject without redrawing or executing the code in the current view body again.

class DataSource: ObservableObject {
    static let shared=DataSource()
    @Published var listOfThings:[String] = ["qwe"]
}

struct View2: View {
    @Binding var views:String
    @ObservedObject var dataSource = DataSource.shared
    var body: some View {
        let _ = print("Loading View2: ",dataSource.listOfThings)
        //let text = dataSource.listOfThings[0]  //Throws ERROR when redraw in background: index out of range
        Text(text)
        Button("Next view"){
            dataSource.listOfThings.popLast()
            // There are other lines of code updating dataSource here
            views="c"
        }
    }
}
struct View3: View {
    @Binding var views:String
    @ObservedObject var dataSource = DataSource.shared
    var body: some View {
        Text(dataSource.listOfThings.joined())
    }
}
struct ContentView: View {
    @State var views = "b"
    var body: some View {
        switch views {
        case "b":
            View2(views: $views)
        case "c":
            View3(views: $views)
        default:
            Text("Not handled view type")
        }
    }
}

Output when reaching View3:

Loading View2:  ["qwe"]

Loading View2:  []  //Ideally this should not be printed

SwiftUI is designed so that the code in your views is run multiple times so that it knows what to display, and it reruns any time a piece of state – whether it's @State , @StateObject , @ObservedObject , etc. – changes. That's why you're seeing multiple print statements – it's expected behaviour.

Here, your View2 and View3 are referencing the same data source, so of course if that data source changes in one, it's going to change in the other.

So when you have a one-element array and remove that element in one view, your other view is going to be re-evaluated because it's looking at exactly the same array. If you don't want that to happen, then you should not share state between views.

The key with successfully using SwiftUI is to ensure that your views' body code can be run, and rerun, as many times as the system wants to without introducing side-effects (the throwing of an error when calling let text = dataSource.listOfThings[0] with an empty array is one such side effect). If you try and fight against that, you'll have a devil of a time creating beautiful and performant user interfaces with SwiftUI.

You can always ditch ObservedObject in the second view as it shouldn't update the UI and doesn't show any data. Just initialize the variable via

var dataSource = DataSource.shared

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM