Пользовательский UITextField, завернутый в UIViewRepresentable, мешающий ObservableObject в NavigationView - PullRequest
0 голосов
/ 20 июня 2020

Контекст

Я создал UIViewRepresentable, чтобы обернуть UITextField, чтобы:

  • он мог быть настроен на то, чтобы он стал первым респондентом при загрузке представления.
  • следующее текстовое поле может быть настроено так, чтобы оно становилось первым ответчиком при нажатии enter

Проблема

При использовании внутри NavigationView, если только клавиатура отклонен из предыдущих представлений, представление не учитывает значение в своих ObservedObject.

Вопрос

Почему это происходит? Что я могу сделать, чтобы исправить это поведение?

Скриншоты

Клавиатура из root вид не отклонен:

enter image description here

Клавиатура из root вид отклонен:

enter image description here

Код

Вот сказанное UIViewRepresentable

struct SimplifiedFocusableTextField: UIViewRepresentable {

    @Binding var text: String
    private var isResponder: Binding<Bool>?
    private var placeholder: String
    private var tag: Int

    public init(
        _ placeholder: String = "",
        text: Binding<String>,
        isResponder: Binding<Bool>? = nil,
        tag: Int = 0
    ) {
        self._text = text
        self.placeholder = placeholder
        self.isResponder = isResponder
        self.tag = tag
    }

    func makeUIView(context: UIViewRepresentableContext<SimplifiedFocusableTextField>) -> UITextField {

        // create textfield
        let textField = UITextField()

        // set delegate
        textField.delegate = context.coordinator

        // configure textfield
        textField.placeholder = placeholder
        textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        textField.tag = self.tag

        // return
        return textField
    }

    func makeCoordinator() -> SimplifiedFocusableTextField.Coordinator {
        return Coordinator(text: $text, isResponder: self.isResponder)
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<SimplifiedFocusableTextField>) {

        // update text
        uiView.text = text

        // set first responder ONCE
        if self.isResponder?.wrappedValue == true && !uiView.isFirstResponder && !context.coordinator.didBecomeFirstResponder{
            uiView.becomeFirstResponder()
            context.coordinator.didBecomeFirstResponder = true
        }
    }

    class Coordinator: NSObject, UITextFieldDelegate {

        @Binding var text: String
        private var isResponder: Binding<Bool>?
        var didBecomeFirstResponder = false

        init(text: Binding<String>, isResponder: Binding<Bool>?) {
            _text = text
            self.isResponder = isResponder
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            text = textField.text ?? ""
        }

        func textFieldDidBeginEditing(_ textField: UITextField) {
            DispatchQueue.main.async {
                self.isResponder?.wrappedValue = true
            }
        }

        func textFieldDidEndEditing(_ textField: UITextField) {
            DispatchQueue.main.async {
                self.isResponder?.wrappedValue = false
            }
        }
    }
}

И для воспроизведения, вот contentView:

struct ContentView: View {
    var body: some View {
        return NavigationView { FieldView(tag: 0) }
    }
}

и вот представление с полем и его моделью представления

struct FieldView: View {

    @ObservedObject private var viewModel = FieldViewModel()
    @State private var focus = false
    var tag: Int

    var body: some View {
        return VStack {
            // listen to viewModel's value
            Text(viewModel.value)

            // text field
            SimplifiedFocusableTextField("placeholder", text: self.$viewModel.value, isResponder: $focus, tag: self.tag)

            // push to stack
            NavigationLink(destination: FieldView(tag: self.tag + 1)) {
                Text("Continue")
            }

            // dummy for tapping to dismiss keyboard
            Color.green
        }
        .onAppear {
            self.focus = true
        }.dismissKeyboardOnTap()
    }
}

public extension View {
    func dismissKeyboardOnTap() -> some View {
        modifier(DismissKeyboardOnTap())
    }
}

public struct DismissKeyboardOnTap: ViewModifier {
    public func body(content: Content) -> some View {
        return content.gesture(tapGesture)
    }

    private var tapGesture: some Gesture {
        TapGesture().onEnded(endEditing)
    }

    private func endEditing() {
        UIApplication.shared.connectedScenes
            .filter {$0.activationState == .foregroundActive}
            .map {$0 as? UIWindowScene}
            .compactMap({$0})
            .first?.windows
            .filter {$0.isKeyWindow}
            .first?.endEditing(true)
    }
}

class FieldViewModel: ObservableObject {
    var subscriptions = Set<AnyCancellable>()
    // diplays
    @Published var value = ""
}

1 Ответ

0 голосов
/ 20 июня 2020

Это снова похоже на движок рендеринга SwiftUI чрезмерно оптимизирован ...

Вот фиксированная часть - просто сделайте пункт назначения уникальным, используя .id. Протестировано с Xcode 11.4 / iOS 13.4

NavigationLink(destination: FieldView(tag: self.tag + 1).id(UUID())) {
    Text("Continue")
}
...