Значение привязки из ObservableObject

Цель:

У меня есть модель ObservableObject. У него есть свойство Bool, я хотел бы использовать это свойство Bool для инициализации переменной @Binding.

Вопросов:

  1. Как преобразовать @ObservableObject в @Binding?
  2. Является ли создание @State единственным способом инициализировать @Binding?

Примечание:

  • Я понимаю, что могу использовать @ObservedObject / @EnvironmentObject, и вижу его полезность, но я не уверен, что простая кнопка должна иметь доступ ко всей модели.
  • Или мое понимание неверно?

Код:

import SwiftUI
import Combine
import SwiftUI
import PlaygroundSupport

class Car : ObservableObject {

    @Published var isReadyForSale = true
}

struct SaleButton : View {

    @Binding var isOn : Bool

    var body: some View {

        Button(action: {

            self.isOn.toggle()
        }) {
            Text(isOn ? "On" : "Off")
        }
    }
}

let car = Car()

//How to convert an ObservableObject to a Binding
//Is creating an ObservedObject or EnvironmentObject the only way to handle a Observable Object ?

let button = SaleButton(isOn: car.isReadyForSale) //Throws a compilation error and rightly so, but how to pass it as a Binding variable ?

PlaygroundPage.current.setLiveView(button)

person user1046037    schedule 10.12.2019    source источник


Ответы (3)


Binding переменные можно создать следующими способами:

  1. @State прогнозируемое значение переменной дает Binding<Value>
  2. Прогнозируемое значение переменной @ObservedObject предоставляет оболочку, из которой вы можете получить Binding<Subject> для всех ее свойств.
  3. Пункт 2 относится также и к @EnvironmentObject.
  4. Вы можете создать переменную Binding, передав замыкания для методов получения и установки, как показано ниже:
let button = SaleButton(isOn: .init(get: { car.isReadyForSale },
                                    set: { car.isReadyForSale = $0} ))

Примечание.

  • Как указал @nayem, вам нужно @State / @ObservedObject / @EnvironmentObject / @StateObject (добавлено в SwiftUI 2.0) в представлении SwiftUI для автоматического обнаружения изменений.
  • Для удобного доступа к прогнозируемым значениям можно использовать префикс $.
person Asperi    schedule 10.12.2019
comment
Я нашел несколько полезных / необходимых ответов, связанных с SwiftUI от @Asperi. - person Hasaan Ali; 17.01.2021
comment
В качестве способа 2 вы можете напрямую получить Binding <Value> по $car.isReadyForSale - person yuanjilee; 20.02.2021
comment
Легко смешивать (как я) $car.isReadyForSale с car.$isReadyForSale. Последний даст Published<Value>.Publisher, чего мы не хотим. (На самом деле, @yuanjilee, это лучший ответ) - person bauerMusic; 12.04.2021

  1. У вас есть несколько вариантов наблюдения за ObservableObject. Если вы хотите быть синхронизированными с состоянием объекта, необходимо наблюдать за состоянием объекта с отслеживанием состояния. Из вариантов наиболее распространенными являются:

    • @State
    • @ObservedObject
    • @EnvironmentObject

Вам решать, какой из них подходит для вашего случая использования.

  1. Нет. Но у вас должен быть объект, за которым можно наблюдать любые изменения, внесенные в этот объект в любой момент времени.

На самом деле у вас будет что-то вроде этого:

class Car: ObservableObject {
    @Published var isReadyForSale = true
}

struct ContentView: View {

    // It's upto you whether you want to have other type 
    // such as @State or @ObservedObject
    @EnvironmentObject var car: Car

    var body: some View {
        SaleButton(isOn: $car.isReadyForSale)
    }

}

struct SaleButton: View {
    @Binding var isOn: Bool
    var body: some View {
        Button(action: {
            self.isOn.toggle()
        }) {
            Text(isOn ? "Off" : "On")
        }
    }
}

Если вы готовы к @EnvironmentObject, вы инициализируете свое представление с помощью:

let contentView = ContentView().environmentObject(Car())
person nayem    schedule 10.12.2019
comment
Спасибо, надеялся, что у кнопки нет доступа ко всей Car модели. Я не мог найти способ создать привязку. Как указал Аспери, у привязки есть инициализатор, который принимает геттер и сеттер. - person user1046037; 10.12.2019
comment
Ну @ user1046037, я это вижу. На самом деле ваша кнопка не будет иметь такого доступа к модели Car, если вы намеренно не введете ее в объект Button. Если вы рассматриваете возможность использования @EnvironmentObject в контейнере / родительском элементе кнопки и не определяете точку доступа к указанному @EnvironmentObject в вашем SaleButton, объект вообще не отображается для объекта кнопки. В остальном, если вы используете @State или @ObservedObject в контейнере, вы не сможете ссылаться на этот объект из своего SaleButton без преднамеренной передачи этого объекта. - person nayem; 10.12.2019
comment
Но я боюсь, сможет ли решение, которое вы указали как @Asperi, справиться с вашим вариантом использования или нет. Потому что очевидно, что ваша кнопка не сможет обновить свой вид, даже если она может изменить состояние объекта автомобиля, поскольку вы не помогаете SwiftUI запоминать состояние вашего экземпляра автомобиля. - person nayem; 10.12.2019
comment
Это правильно, однако это открывает возможность не передавать всю модель кнопке, а просто передать только замыкание, которое будет обновляться. Я согласен с тем, что необходимо использовать @ObservedObject, однако я никогда не знал, что существует способ преодолеть разрыв от Observable Object до Binding (путем создания привязки из закрытия get / set). Ваше решение работает без прохождения всей модели - person user1046037; 10.12.2019
comment
Хорошо, я понял вашу точку зрения. Имейте в виду, что для создания Binding не требуется, чтобы объект, используемый в закрытии get-set, был ObservableObject. По этой причине можно использовать даже объекты типа значения. - person nayem; 10.12.2019
comment
Я не уверен, что понимаю, вы имеете в виду простые быстрые свойства? Я хотел что-то, что обновило бы мою модель, не уверен, как мне поможет тип значения. Приятно, что в среде есть способ связать переменную с привязкой, но наблюдаемый объект должен сделать это через закрытие. - person user1046037; 10.12.2019
comment
Да! Вы также можете попробовать это сами. Просто сделайте свой Car структурой. А для целей тестирования вы можете добавить наблюдатель свойств didSet в свойство объекта структуры. - person nayem; 10.12.2019

struct ContentView: View {
    @EnvironmentObject var car: Car

    var body: some View {
        SaleButton(isOn: self.$car.isReadyForSale)
    }
}

class Car: ObservableObject {
    @Published var isReadyForSale = true
}

struct SaleButton: View {
    @Binding var isOn: Bool

    var body: some View {
        Button(action: {
            self.isOn.toggle()
        }) {
            Text(isOn ? "On" : "Off")
        }
    }
}

Убедитесь, что в вашем SceneDelegate есть следующее:

// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
    .environmentObject(Car())
person fulvio    schedule 10.12.2019
comment
Спасибо, я полностью согласен, что мы можем сделать через State, интересно, есть ли способ обойтись без использования State (как указано в вопросе) - person user1046037; 10.12.2019
comment
@ user1046037 Он делает это без State. - person fulvio; 10.12.2019
comment
Он использует объект среды, как указано в вопросе, который я надеялся не использовать EnvironmentObject или ObservedObject, потому что я не хотел, чтобы вся модель была доступна для простой кнопки. - person user1046037; 10.12.2019