Пользовательский вид SwiftUI с двусторонней привязкой - PullRequest
0 голосов
/ 18 февраля 2020

Я изо всех сил пытаюсь реализовать пользовательское представление, которое может принять Binding в качестве аргумента, и реализовать двустороннее обновление этого значения.

Так что в основном я реализую свой пользовательский ползунок и хочу, чтобы его инициализатор был например:

MySlider(value: <Binding<Float>)


С чем я борюсь:

  1. Как подписаться на удаленные обновления значения привязки, чтобы Я могу обновить состояние представления?
  2. Есть ли хороший способ связать привязку со свойством @State?

Вот моя текущая реализация, которая пока не идеальна.

struct MySlider: View {

    @Binding var selection: Float?
    @State private var selectedValue: Float?

    init(selection: Binding<Float?>) {
        self._selection = selection

        // https://stackoverflow.com/a/58137096
        _selectedValue = State(wrappedValue: selection.wrappedValue)
    }

    var body: some View {
         HStack(spacing: 3) {
             ForEach(someValues) { (v) in
                 Item(value: v,
                      isSelected: v == self.selection)
                     .onTapGesture {
                         // No idea how to do that other way so I don't have to set it twice
                         self.selection = v
                         self.selectedValue = v
                    }
             }
         }
    }
}

Редактировать 1: Полагаю, моя проблема в том, что базовый объект модели взят из Базовых данных, а не принадлежал ни одному представлению SwiftUI, которое наблюдало бы его изменения. Объект модели принадлежал UIKit ViewController, и я передавал только привязку к представлению SwiftUI, что недостаточно. Мое решение теперь состоит в том, чтобы передать объект модели также в SwiftUI View, чтобы он мог пометить его как @ObservedObject.

struct MySlider<T>: View where T: ObservableObject {

    @ObservedObject var object: T
    @Binding var selection: Float?

    var body: some View {
        return ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 3) {
                ForEach(values) { (v) in
                    Item(value: v,
                         isSelected: v == self.selection)
                        .onTapGesture {
                            self.selection = v
                    }
                }
            }
        }
    }
}

1 Ответ

0 голосов
/ 18 февраля 2020

Определение @Binding по сути является двусторонним соединением с базовым элементом данных, таким как @State переменная, которая принадлежит другому представлению. Как указано Apple :

Используйте привязку для создания двустороннего соединения между представлением и его базовой моделью.

Если это привязывая, SwiftUI уже обновит свои представления автоматически, если значение изменится; поэтому (в ответ на ваш первый вопрос) вам не нужно делать какие-либо подписки для обновления своего пользовательского представления - это произойдет автоматически.

Аналогично (относительно вашего второго вопроса), поскольку привязка состояние из другого представления, вы не должны также объявлять его как состояние для своего представления, и я также не верю, что это возможно. Состояние - это то, что должно быть чисто внутренним значением для вашего взгляда (Apple настоятельно рекомендует, чтобы все свойства @State были объявлены private).

Все это связано с концепцией «Единого источника истины» Apple подчеркнул при представлении SwiftUI: родительское представление, где эта привязка уже @State, является тем, что владеет информацией, так что это не то, что ваше представление также должно объявлять как состояние.

Для вашего кода, я думаю, все, что вам нужно сделать это второе свойство государства, потому что это не требуется. Просто убедитесь, что передаваемая привязка является свойством @State в любом родительском представлении, которому принадлежит ваше настраиваемое представление, и передайте его, используя синтаксис $, для создания этой привязки. Эта статья раскрывает идею более подробно, если вам нужно.

struct MySlider: View {

    @Binding var selection: Float?

    init(selection: Binding<Float?>) {
        self._selection = selection
    }

    var body: some View {
         HStack(spacing: 3) {
             ForEach(someValues) { (v) in
                 Item(value: v, isSelected: v == self.selection)
                     .onTapGesture {
                         self.selection = v
                    }
             }
         }
    }
}
...