-2

I'm trying to get into swift/swiftui but I'm really struggling with this one:

I have a MainView containing a ChildView. The ChildView has a function update to fetch the data to display from an external source and assign it to a @State data variable.

I'd like to be able to trigger update from MainView in order to update data.

I've experienced that update is in fact called, however, data is reset to the initial value upon this call.

The summary of what I have:

struct ChildView: View {
    
    @State var data: Int = 0

     var body: some View {
       Text("\(data)")

        Button(action: update) {
            Text("update") // works as expected 
        }
     }

    func update() {
        // fetch data from external source 
        data = 42
    }
}
struct MainView: View {
    var child = ChildView()
 
    var body: some View {
        VStack {
           child
           
           Button(action: {
              child.update()
           }) {
              Text("update")  // In fact calls the function, but doesn't set the data variable to the new value
           }
        }
    }
}

When googling for a solution, I only came across people suggesting to move update and data to MainView and then pass a binding of data to ChildView.

However, following this logic I'd have to blow up MainView by adding all the data access logic in there. My point of having ChildView at all is to break up code into smaller chunks and to reuse ChildView including the data access methods in other parent views, too. I just cannot believe there's no way of doing this in SwiftUI.

gevaye
  • 1
  • 2
  • You should try the Apple SwiftUI tutorials. It isn’t like other frameworks. You can’t hold on to the view in a variable and expect it to work like others work. You can reuse the data that the views can react to but not the views, SwiftUI recreates them and reloads when it deems necessary. – lorem ipsum Feb 17 '23 at 13:45
  • I'm afraid you'll have to be more specific with me. What's the general concept to achieve what I want in SwiftUI? Can you give me a buzzword or point me to a specific tutorial? – gevaye Feb 17 '23 at 13:50
  • What you want isn’t viable with SwiftUI. The “Apple SwiftUI tutorials” will give you the basics. – lorem ipsum Feb 17 '23 at 13:54
  • Then what's the Swift way to have interacting modules? I've been searching for this for quite a while now. Merely referring me to "the apple tutorials" isn't helpful at all. – gevaye Feb 17 '23 at 14:00
  • That is literally what they are called, if you Google those exact words you will get a set of tutorials Apple has provided, the very first link. You can’t have views in variables as you are trying to do. – lorem ipsum Feb 17 '23 at 14:06
  • 1. I know where to find those tutorials and I've been practicing with them before. What I don't know, what I didn't find out using them, and what I'm asking for here is how to have interacting views or – alternatively – how to achieve the same result by other means. 2. Other tutorials clearly say one can have views in variables, e.g. [here](https://www.hackingwithswift.com/quick-start/swiftui/how-to-store-views-as-properties) and 3. Why would you even comment if you at least pretend to know to help me but don't want to?! – gevaye Feb 17 '23 at 14:15
  • This setup will not work, you have to change the data using models and use the appropriate wrappers. There is no way to do what you want because SwiftUI hides what it is doing, the instance of the view you are holding on to does not know when to update because it is outside a ViewBuilder. – lorem ipsum Feb 17 '23 at 14:21

1 Answers1

2

Is completely understandable to be confused at first with how to deal with state on SwiftUI, but hang on there, you will find your way soon enough.

What you want to do can be achieved in many different ways, depending on the requirements and limitations of your project. I will mention a few options, but I'm sure there are more, and all of them have pros and cons, but hopefully one can suit your needs.

Binding

Probably the easiest would be to use a @Binding, here a good tutorial/explanation of it.

An example would be to have data declared on your MainView and pass it as a @Binding to your ChildView. When you need to change the data, you change it directly on the MainView and will be reflected on both.

This solutions leads to having the logic on both parts, probably not ideal, but is up to what you need.

Also notice how the initialiser for ChildView is directly on the body of MainView now.

Example

struct ChildView: View {
    
    @Binding var data: Int

     var body: some View {
       Text("\(data)")

        Button(action: update) {
            Text("update") // works as expected 
        }
     }

    func update() {
        // fetch data from external source 
        data = 42
    }
}
struct MainView: View {
    @State var data: Int = 0
 
    var body: some View {
        VStack {
           ChildView(data: $data)
           
           Button(action: {
              data = 42
           }) {
              Text("update")  // In fact calls the function, but doesn't set the data variable to the new value
           }
        }
    }
}

ObservableObject

Another alternative would be to remove state and logic from your views, using an ObservableObject, here an explanation of it.

Example

class ViewModel: ObservableObject {
    @Published var data: Int = 0

    func update() {
        // fetch data from external source 
        data = 42
    }
}
struct ChildView: View {
    @ObservedObject var viewModel: ViewModel

     var body: some View {
       Text("\(viewModel.data)")

        Button(action: viewModel.update) {
            Text("update") // works as expected 
        }
     }
}
struct MainView: View {
    @StateObject var viewModel = ViewModel()
 
    var body: some View {
        VStack {
           ChildView(viewModel: viewModel)
           
           Button(action: {
              viewModel.update()
           }) {
              Text("update")  // In fact calls the function, but doesn't set the data variable to the new value
           }
        }
    }
}
vicegax
  • 4,709
  • 28
  • 37