Как сделать двустороннюю привязку внутри класса?

Используя @EnvironmentObject, я создал класс, который наследуется от ObservableObject и является центром для глобальных переменных @Published. Однако я не мог создать двустороннюю привязку внутри класса. Как я могу этого добиться? $ Не работает в классах, потому что он работает только с @State, а @State не работает в классах. Мне нужна двусторонняя привязка, потому что каждый дочерний экземпляр должен иметь изменение данных в реальном времени положения экрана, когда пользователь перемещает дочерний экземпляр по экрану. Вот мой код:

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
    }
}

person Rich    schedule 20.07.2020    source источник


Ответы (1)


У вас есть именно то, что я сделал бы, но вместо этого я бы использовал настраиваемую привязку для 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
    }
}

Это в основном использует настраиваемую привязку для достижения вашей цели.

person Muhand Jumah    schedule 20.07.2020
comment
О, это работает! Ха! Отличная работа, @Muhand! Ты сделал это снова! Я думаю, что ваш опыт работы с серверной частью действительно поможет вам увидеть, что здесь происходит под капотом. - person Rich; 21.07.2020
comment
Вместо отдельного массива childInstanceData могу ли я подключиться к каждому экземпляру из другого представления и получить данные переменной currentPosition каждого экземпляра в режиме реального времени? Я могу получить его для отображения в представлении Child, например, в Text (self.currentPosition), и он действительно показывает изменение положения каждого представления Child на экране в реальном времени. Мне просто нужны необработанные данные о положении на экране для каждого элемента в другом представлении. - person Rich; 21.07.2020
comment
Я рада, что снова смогла помочь! если вы считаете, что это решило вашу проблему, отметьте его как правильное, чтобы другие могли легко найти ответ! - person Muhand Jumah; 21.07.2020
comment
Чтобы ответить на ваш второй вопрос, я бы сказал, что да, безусловно, выполнимо. Однако есть два подхода к этой проблеме, один из них - создать объект, и вместо передачи Child View в массив вы передадите фактические объекты, и каждый раз, когда вы представляете дочерний элемент, вам потребуется привязка, которая ссылается на этот массив - person Muhand Jumah; 21.07.2020
comment
Второй метод - сохранить то, что у вас есть прямо сейчас, но для каждого представления, в котором вы хотите отобразить дочерний элемент, передать @Binding var index:Int, который является индексом дочернего элемента, а в другом представлении отобразить результат, вызвав DataBridge.childInstanceData[index], но я бы очень хотел рекомендую первый способ. поскольку вы уменьшите количество вещей, которые вы будете отслеживать. Каждый объект должен включать как минимум ID типа UUID и Data типа CGSize. - person Muhand Jumah; 21.07.2020
comment
Большое спасибо, дружище! Я собираюсь попробовать их и посмотреть, как они работают. - person Rich; 21.07.2020
comment
Пожалуйста, дайте мне знать, если вам понадобится дополнительная помощь. Буду рад помочь. - person Muhand Jumah; 21.07.2020
comment
Да, @Muhand, мне нужна дополнительная помощь, спасибо! Не могли бы вы подробнее рассказать о своем первом методе? Когда вы говорите «объект», вы имеете в виду создать класс и присвоить его переменной, объекту таким образом? Как мне сделать данные представления в классе, как я сделал в структуре? Можете ли вы расширить свой первый метод? Я задал здесь новый вопрос: stackoverflow.com/questions/63023528/ Спасибо! - person Rich; 22.07.2020
comment
Конечно, я посмотрю на пост и напишу что-нибудь чистое, что можно использовать, и добавлю его в качестве ответа, а также объясню, не беспокойтесь. - person Muhand Jumah; 22.07.2020