【问题标题】:Is there a way to show the system keyboard and take inputs from it without a SwiftUI TextField?有没有办法在没有 SwiftUI TextField 的情况下显示系统键盘并从中获取输入?
【发布时间】:2021-12-29 19:52:37
【问题描述】:

我希望能够显示系统键盘,并且我的应用可以在不使用 TextField 或类似名称的情况下从键盘获取输入。我的简单示例应用如下:

struct TypingGameView: View {
   let text = “Some example text”
   @State var displayedText: String = ""

   var body: some View {
      Text(displayedText)
   }
}

我正在制作一个记忆应用程序,所以当我用户在键盘上输入输入时,它应该从text 中获取下一个单词并将其添加到displayedText 以显示在屏幕上。显示视图时,键盘应该会自动弹出。

如果有,原生 SwiftUI 解决方案会很棒,可能如下:

struct TypingGameView: View {
   let text = “Some example text”
   @State var displayedText: String = ""

   var body: some View {
      Text(displayedText)
         .onAppear {
            showKeyboard()
         }
         .onKeyboardInput { keyPress in
            displayedText += keyPress
         }
   }
}

如果有某种方法,TextField 可以工作 1. 使输入的任何内容都不会显示在 TextField 中,2. 禁用点击文本(例如移动光标或选择),3.禁用删除文本。

【问题讨论】:

  • 我在 SwiftUI 上做得很少,但是有没有办法在屏幕边界之外放置一个 TextField,这样可以满足您的需要吗?
  • @PhillipMills 可能,它不是最优雅的解决方案,所以如果 SwiftUI 中内置了一些用于此功能的东西,我宁愿采用它,但可能只需要将它放在屏幕之外如果不存在其他解决方案,则限制/隐藏某些东西。

标签: swift swiftui


【解决方案1】:

这是使用UIViewRepresentable 的可能解决方案:

  • 创建UIView 的子类,实现UIKeyInput 但不绘制任何内容
  • 将其包装在实现 UIViewRepresentable 的结构中,使用 Coordinator 作为自定义 UIView 的委托,以“上游”携带编辑后的文本
  • 再次将其包装在显示内容的 ViewModifier 中,将绑定传递给包装器并在点击时触发自定义 UIView 的第一响应者

我确信有一个更综合的解决方案可以找到,这样一个简单问题的三个类似乎有点多。

protocol InvisibleTextViewDelegate {
    func valueChanged(text: String?)
}

class InvisibleTextView: UIView, UIKeyInput {
    var text: String?
    var delegate: InvisibleTextViewDelegate?

    override var canBecomeFirstResponder: Bool { true }

    // MARK: UIKeyInput
    var keyboardType: UIKeyboardType = .decimalPad
    
    var hasText: Bool { text != nil }

    func insertText(_ text: String) {
        self.text = (self.text ?? "") + text
        setNeedsDisplay()
        delegate?.valueChanged(text: self.text)
    }

    func deleteBackward() {
        if var text = text {
            _ = text.popLast()
            self.text = text
        }
        setNeedsDisplay()
        delegate?.valueChanged(text: self.text)
    }
}

struct InvisibleTextViewWrapper: UIViewRepresentable {
    typealias UIViewType = InvisibleTextView
    @Binding var text: String?
    @Binding var isFirstResponder: Bool
    
    class Coordinator: InvisibleTextViewDelegate {
        var parent: InvisibleTextViewWrapper
        
        init(_ parent: InvisibleTextViewWrapper) {
            self.parent = parent
        }
        
        func valueChanged(text: String?) {
            parent.text = text
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> InvisibleTextView {
        let view = InvisibleTextView()
        view.delegate = context.coordinator
        return view
    }
    
    func updateUIView(_ uiView: InvisibleTextView, context: Context) {
        if isFirstResponder {
            uiView.becomeFirstResponder()
        } else {
            uiView.resignFirstResponder()
        }
    }
    
    
}

struct EditableText: ViewModifier {
    @Binding var text: String?
    @State var editing: Bool = false
    
    func body(content: Content) -> some View {
        content
            .background(InvisibleTextViewWrapper(text: $text, isFirstResponder: $editing))
            .onTapGesture {
                editing.toggle()
            }
            .background(editing ? Color.gray : Color.clear)
    }
}

extension View {
    func editableText(_ text: Binding<String?>) -> some View {
        modifier(EditableText(text: text))
    }
}


struct CustomTextField_Previews: PreviewProvider {
    struct Container: View {
        @State private var value: String? = nil
        
        var body: some View {
            HStack {
                if let value = value {
                    Text(value)
                    Text("meters")
                        .font(.subheadline)
                } else {
                    Text("Enter a value...")
                }
            }
            .editableText($value)
        }
    }
    
    static var previews: some View {
        Group {
            Container()
        }
    }
}

【讨论】:

  • 这绝对是有帮助的,并且是我正在寻找的正确方向,尽管我认为那时它会更简单,并且会使用更原生的方法来使用 TextField并将其隐藏在某些元素后面。
  • 我认为这真的取决于上下文。您可以通过这种方式更好地控制正在发生的事情,甚至可以构建您要求的 API。 TextField 在幕后使用 UITextField,所以我不确定是否有一种解决方案比另一种更惯用。
  • 公平一点,我将分叉我的项目并尝试实现它,看看它是否是我正在寻找的,谢谢:)
【解决方案2】:

您可以在屏幕上显示文本字段,但如果您不希望它显示,请将不透明度设置为 0。虽然这不能解决防止删除文本的问题

然后您可以使用类似https://stackoverflow.com/a/56508132/3919220 的方式以编程方式强制它成为第一响应者

【讨论】:

  • 感谢您的回复,但TextField 仍然是交互式的,我不想要。我正在寻找不使用TextField(如果可能)的解决方案。
猜你喜欢
  • 2011-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-24
  • 2013-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多