Получение привязки из существующего SwiftUI @States

Я поиграл с SwiftUI и Combine и чувствую, что, возможно, есть способ получить существующие свойства @State в представлении и создать новое.

Например, у меня есть представление создания пароля, которое содержит пароль и поле passwordConfirm для пользователя. Я хочу взять эти два свойства @State и получить новое @State, которое я могу использовать в своем представлении, которое подтверждает, является ли ввод действительным. Так что для простоты: не пустой и не равный.

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

Это неработающий псевдокод:

import SwiftUI
import Combine

struct CreatePasswordView : View {
    @State var password = ""
    @State var confirmation = ""
    lazy var valid = {
        return self.$password.publisher()
            .combineLatest(self.$confirmation)
            .map { $0 != "" && $0 == $1 }
    }

    var body: some View {
        SecureField($password, placeholder: Text("password"))

        SecureField($confirmation, placeholder: Text("confirm password"))

        NavigationButton(destination: NextView()) { Text("Done") }
            .disabled(!valid)
    }
}

Кто-нибудь нашел. подходящий способ сделать это / если это возможно?

ОБНОВЛЕНИЕ Бета 2:

Начиная с бета-версии 2 издатель доступен, поэтому первая половина этого кода теперь работает. Вторую половину использования получившегося издателя в представлении я до сих пор не выяснил (disabled(!valid)).

import SwiftUI
import Combine

struct CreatePasswordView : View {
    @State var password = ""
    @State var confirmation = ""

    lazy var valid = {
        Publishers.CombineLatest(
            password.publisher(),
            confirmation.publisher(),
            transform: { String($0) != "" && $0 == $1 }
        )
    }()

    var body: some View {
        SecureField($password, placeholder: Text("password"))

        SecureField($confirmation, placeholder: Text("confirm password"))

        NavigationButton(destination: NextView()) { Text("Done") }
            .disabled(!valid)
    }
}

Благодарю.

2 ответа

Я не буду играть с @State/@Published как Combine в настоящее время находится в бета-версии, но вот простой обходной путь для того, чего вы пытаетесь достичь.

Я бы реализовал модель представления для хранения пароля, подтверждения пароля и того, действителен ли он или нет

class ViewModel: NSObject, BindableObject {

    var didChange = PassthroughSubject<Void, Never>()

    var password: String = "" {
        didSet {
            didChange.send(())
        }
    }

    var passwordConfirmation: String = "" {
        didSet {
            didChange.send(())
        }
    }

    var isPasswordValid: Bool {
        return password == passwordConfirmation && password != ""
    }

}

Таким образом, представление пересчитывается каждый раз, когда изменяется пароль или подтверждение.

Тогда я бы сделал @ObjectBinding для просмотра модели.

struct CreatePasswordView : View {

    @ObjectBinding var viewModel: ViewModel

    var body: some View {
        NavigationView {
            VStack {
                SecureField($viewModel.password,
                            placeholder: Text("password"))
                SecureField($viewModel.passwordConfirmation,
                            placeholder: Text("confirm password"))
                NavigationButton(destination: EmptyView()) { Text("Done") }
                    .disabled(!viewModel.isPasswordValid)
            }
        }
    }

}

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

Здесь вам нужно вычисленное свойство . Как следует из названия, его значение пересчитывается при каждом доступе к свойству. Если вы используете @State переменных для вычисления свойства, SwiftUI будет автоматически пересчитывать тело представления всякий раз, когда valid изменено:

      struct CreatePasswordView: View {
    @State var password = ""
    @State var confirmation = ""

    private var valid: Bool {
        password != "" && password == confirmation
    }

    var body: some View {
        SecureField($password, placeholder: Text("password"))

        SecureField($confirmation, placeholder: Text("confirm password"))

        NavigationLink(destination: NextView()) { Text("Done") }
            .disabled(!valid)
    }
}
Другие вопросы по тегам