这是我的实现的变体,基于@Mojtaba Hosseini和@Matteo Pacini解决方案。我还是SwiftUI的新手,所以我不能保证代码的绝对正确性,但是可以正常工作。
我希望这会对某人有所帮助。
ResponderView:这是一个通用的响应者视图,可以与任何UIKit视图一起使用。
struct ResponderView<View: UIView>: UIViewRepresentable {
    @Binding var isFirstResponder: Bool
    var configuration = { (view: View) in }
    func makeUIView(context: UIViewRepresentableContext<Self>) -> View { View() }
    func makeCoordinator() -> Coordinator {
        Coordinator($isFirstResponder)
    }
    func updateUIView(_ uiView: View, context: UIViewRepresentableContext<Self>) {
        context.coordinator.view = uiView
        _ = isFirstResponder ? uiView.becomeFirstResponder() : uiView.resignFirstResponder()
        configuration(uiView)
    }
}
extension ResponderView {
    final class Coordinator {
        @Binding private var isFirstResponder: Bool
        private var anyCancellable: AnyCancellable?
        fileprivate weak var view: UIView?
        init(_ isFirstResponder: Binding<Bool>) {
            _isFirstResponder = isFirstResponder
            self.anyCancellable = Publishers.keyboardHeight.sink(receiveValue: { [weak self] keyboardHeight in
                guard let view = self?.view else { return }
                DispatchQueue.main.async { self?.isFirstResponder = view.isFirstResponder }
            })
        }
    }
}
extension Publishers {
    static var keyboardHeight: AnyPublisher<CGFloat, Never> {
        let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
            .map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 }
        let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
            .map { _ in CGFloat(0) }
        return MergeMany(willShow, willHide)
            .eraseToAnyPublisher()
    }
}
struct ResponderView_Previews: PreviewProvider {
    static var previews: some View {
        ResponderView<UITextField>.init(isFirstResponder: .constant(false)) {
            $0.placeholder = "Placeholder"
        }.previewLayout(.fixed(width: 300, height: 40))
    }
}
ResponderTextField-这是围绕ResponderView的方便的文本字段包装器。
struct ResponderTextField: View {
    var placeholder: String
    @Binding var text: String
    @Binding var isFirstResponder: Bool
    private var textFieldDelegate: TextFieldDelegate
    init(_ placeholder: String, text: Binding<String>, isFirstResponder: Binding<Bool>) {
        self.placeholder = placeholder
        self._text = text
        self._isFirstResponder = isFirstResponder
        self.textFieldDelegate = .init(text: text)
    }
    var body: some View {
        ResponderView<UITextField>(isFirstResponder: $isFirstResponder) {
            $0.text = self.text
            $0.placeholder = self.placeholder
            $0.delegate = self.textFieldDelegate
        }
    }
}
private extension ResponderTextField {
    final class TextFieldDelegate: NSObject, UITextFieldDelegate {
        @Binding private(set) var text: String
        init(text: Binding<String>) {
            _text = text
        }
        func textFieldDidChangeSelection(_ textField: UITextField) {
            text = textField.text ?? ""
        }
    }
}
struct ResponderTextField_Previews: PreviewProvider {
    static var previews: some View {
        ResponderTextField("Placeholder",
                           text: .constant(""),
                           isFirstResponder: .constant(false))
            .previewLayout(.fixed(width: 300, height: 40))
    }
}
以及使用方式。
struct SomeView: View {
    @State private var login: String = ""
    @State private var password: String = ""
    @State private var isLoginFocused = false
    @State private var isPasswordFocused = false
    var body: some View {
        VStack {
            ResponderTextField("Login", text: $login, isFirstResponder: $isLoginFocused)
            ResponderTextField("Password", text: $password, isFirstResponder: $isPasswordFocused)
        }
    }
}