1

Using @EnvironmentObject I created a class that inherits from ObservableObject and is the hub for @Published global variables. I couldn't create a two way binding from within the class though. How can I accomplish that? The $ doesn't work in classes because it only works with @State, and @State doesn't work in classes. I need two-way binding because each child instance must have real-time data change of the screen position when the user moves a child instance around the screen. Here is my code:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var settings: DataBridge
    
    var body: some View {
        ZStack {
            ForEach(self.settings.childInstances.indices , id: \.self) { index in
                self.settings.childInstances[index]
            }
            VStack {
                ForEach(self.settings.childInstanceData.indices, id: \.self) { index in
                    Text("\(index). y: \(self.settings.childInstanceData[index].height) : x: \(self.settings.childInstanceData[index].width)")
                }
            }
            .offset(y: -250)
            
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import SwiftUI

struct Child: View {
    @EnvironmentObject var settings: DataBridge
    @Binding var stateBinding: CGSize
    
    @State var isInitalDrag = true
    @State var isOnce = true
    
    @State var currentPosition: CGSize = .zero
    @State var newPosition: CGSize = .zero
    
    var body: some View {
        Circle()
            .frame(width: 50, height: 50)
            .foregroundColor(.blue)
            .offset(self.currentPosition)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        
                        if self.isInitalDrag && self.isOnce {
                            
                            // Call function in DataBridge here:
                            self.settings.addChild()
                            
                            self.isOnce = false
                        }
                        
                        self.currentPosition = CGSize(
                            width: CGFloat(value.translation.width + self.newPosition.width),
                            height: CGFloat(value.translation.height + self.newPosition.height)
                        )
                        
                        self.stateBinding = self.currentPosition
                    }
                    .onEnded { value in
                        self.newPosition = self.currentPosition
                        
                        self.isOnce = true
                        self.isInitalDrag = false
                    }
            )
    }
}

struct Child_Previews: PreviewProvider {
    static var previews: some View {
        Child(stateBinding: .constant(.zero))
    }
}
class DataBridge: ObservableObject {
    @Published var childInstances: [Child] = []
    @Published var childInstanceData: [CGSize] = []
    @Published var childIndex = 0
    func addChild() {
        self.childInstanceData.append(.zero)
        
        // This where I want two-way binding with the childInstanceData array:
        self.childInstances.append(Child(stateBinding: $childInstanceData[childIndex]))
        
        self.childIndex += 1
    }
}
Rich
  • 57
  • 1
  • 6

1 Answers1

1

What you have is exactly what I would do, but instead I would use custom Binding for the Child

class DataBridge: ObservableObject {
    @Published var childInstances: [Child] = []
    @Published var childInstanceData: [CGSize] = []
    @Published var childIndex = 0
    func addChild() {
        self.childInstanceData.append(.zero)
        
        // This where I want two-way binding with the childInstanceData array:
        let child = Child(stateBinding: Binding(get: {
            return self.childInstanceData[self.childIndex]
        }, set: {
            self.childInstanceData[self.childIndex] = $0
        }))

        self.childInstances.append(child)
        
        self.childIndex += 1
    }
}

This basically uses custom binding to achieve your goal.

Muhand Jumah
  • 1,726
  • 1
  • 11
  • 26
  • Oh, it's working! Ha! Nice job, @Muhand! You did it again! I think your expertise of back-end really gets you to see what's going on under the hood here. – Rich Jul 20 '20 at 23:06
  • Instead of a separate childInstanceData array is there a way for me to tap into each instance from a different view and get the currentPosition variable data of each instance in real time? I am able to get it to display in the Child's view, like in a Text(self.currentPosition), and it does show real time changing of each Child view's position on the screen. I just need to have that raw screen position data for each element all in a different view. – Rich Jul 20 '20 at 23:06
  • I am glad I was able to help once again! if you think it solved your issue, consider marking it correct so others can easily find the answer! – Muhand Jumah Jul 20 '20 at 23:18
  • To answer your second question, I would say yes definitely doable. There are 2 approaches to this problem however, one method would be to create an object and instead of passing a `Child` `View` to the array you would pass the actual objects and everytime you present the child you would need a Binding that reference back to this array – Muhand Jumah Jul 20 '20 at 23:19
  • The second method would be to keep what you have right now but to every view that you want to display the child in, pass a `@Binding var index:Int` which is the index of the child and in the other view render the result by calling `DataBridge.childInstanceData[index]` but I would highly recommend the first method. as you will reduce the amount of things you will keep track of. Each object must include an `ID` of type `UUID` and `Data` of type `CGSize` at minimum. – Muhand Jumah Jul 20 '20 at 23:22
  • Thanks a lot, buddy! I'm going to give these a try and see how they do. – Rich Jul 21 '20 at 01:55
  • You are welcome, let me know if you need further help with it. I will be happy to help. – Muhand Jumah Jul 21 '20 at 03:03
  • Yes @Muhand, I could use more help, thx! Can you elaborate more on your first method, please? When you say object you mean create a class and assign it to a variable, an object that way? How do I make the view data in the class like I did in the struct? Can you expand on your first method? I made a new question here: https://stackoverflow.com/questions/63023528/real-time-data-changes-using-two-way-binding-for-each-instance-in-swiftui Thank you! – Rich Jul 21 '20 at 21:46
  • Of course, I will take a look at the post and I will write something clean that can be used and add it as an answer and explain it as well, no worries. – Muhand Jumah Jul 21 '20 at 22:22