包装在UIViewRepresentable中的自定义UITextField会干扰NavigationView中的ObservableObject

时间:2020-06-20 02:49:20

标签: swiftui

上下文

我创建了一个UIViewRepresentable来包裹UITextField,以便:

  • 可以将其设置为在加载视图时成为第一响应者。
  • 按下enter时,下一个文本字段可以设置为第一响应者

问题

NavigationView中使用时,除非从以前的视图中解雇了键盘,否则该视图不会观察其ObservedObject中的值。

问题

为什么会这样?我该怎么做才能解决此问题?

屏幕截图

未从根视图中清除键盘:

enter image description here

从根视图的键盘已关闭:

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 :(得分:0)

看起来又像SwiftUI渲染引擎过度优化 ...

这是固定的部分-只需使用heroku config set:DISABLE COLLECTSTATIC=0强制使目的地唯一。经过Xcode 11.4 / iOS 13.4的测试

.id