使用自定义按钮扩展SwiftUI键盘

29

我正在尝试找到一种方法,向SwiftUI numberPad添加一个键或按钮。我找到的唯一参考资料都说这是不可能的。在Swift世界中,我添加了一个工具栏,其中包含一个按钮,用于关闭键盘或执行其他功能。

我甚至会构建一个ZStack视图,并将按钮置于其上,但我找不到将numberPad添加到自己的视图中的方法。

在这种情况下,我真正想做的就是在输入数据后关闭numberPad。我最初尝试修改SceneDelegate以在点击时关闭,但仅当我在另一个文本或文本字段视图中而不是在视图上的空白处点击时才有效。

window.rootViewController = UIHostingController(rootView: contentView.onTapGesture {
    window.endEditing(true)
})

理想情况下,我会在左下角添加一个“完成”键。如果可以在SwiftUI中实现,则次佳方案是添加工具栏。

enter image description here

任何指导都将不胜感激。
Xcode 版本 11.2.1 (11B500)。
4个回答

28
使用UIRepresentable协议解决了问题。
struct TestTextfield: UIViewRepresentable {
    @Binding var text: String
    var keyType: UIKeyboardType
    func makeUIView(context: Context) -> UITextField {
        let textfield = UITextField()
      textfield.keyboardType = keyType
        let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: textfield.frame.size.width, height: 44))
        let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(textfield.doneButtonTapped(button:)))
        toolBar.items = [doneButton]
        toolBar.setItems([doneButton], animated: true)
        textfield.inputAccessoryView = toolBar
        return textfield
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
        
    }
}

extension  UITextField{
    @objc func doneButtonTapped(button:UIBarButtonItem) -> Void {
       self.resignFirstResponder()
    }

}

在内容视图中使用

struct ContentView : View {
    @State var text = ""
    
    var body: some View {
        TestTextfield(text: $text, keyType: UIKeyboardType.phonePad)
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 50)
            .overlay(
                RoundedRectangle(cornerRadius: 16)
                    .stroke(Color.blue, lineWidth: 4)
        )
    }
}

2
我本来希望有一个纯SwiftUI的解决方案,但我认为你是正确的——并没有这样的解决方案。 - JohnSF
1
最好通过 makeCoordinator() 实例化协调器并将其用于委托和选择器目标。我同意其他评论,目前似乎还不存在纯 SwiftUI 的解决方案。 - Bjørn Olav Ruud
1
尝试实现上述代码时,当选择“完成”时,@Binding text 值不会传递到原始的 @State 文本值。我错过了什么? - Gloorfindel
1
值在点击完成按钮时未传递,请检查绑定。 - Rajeev Kumar S
1
此解决方案不支持SwiftUI TextField Label属性,因此在SwiftUI表单中无法完全工作。 - Steve Macdonald
显示剩余2条评论

15

iOS 15+

从iOS 15开始,我们可以使用新的keyboard ToolbarItemPlacement,在键盘上方添加自定义视图:

Form {
    ...
}
.toolbar {
    ToolbarItem(placement: .keyboard) {
        Button("Do something") {
            print("something")
        }
    }
}

您还可以将此与新的@FocusState属性包装器结合使用,以从当前编辑的元素中移除焦点:

@FocusState private var isFocused: Bool
...

var body: some View {
    Form {
        TextField(...)
            .focused($isFocused)
    }
    .toolbar {
        ToolbarItem(placement: .keyboard) {
            Button("Done") {
                isFocused = false
            }
        }
    }
}

你有没有想过如何使工具栏永久可见,即使TextField没有焦点或键盘被关闭后? - Tuan
你可以使用另一个放置方式,例如bottomBar - pawello2222
1
bottomBar完全不同,不能用于此。 - Tuan

15

我使用SwiftUI-Introspect库,在5行代码内完成了它!我遇到了一个问题,即Representable Text Field对填充和框架没有任何反应。一切都是使用这个库解决的!

链接: https://github.com/siteline/SwiftUI-Introspect

import SwiftUI
import Introspect

struct ContentView : View {
@State var text = ""

var body: some View {
    TextField("placeHolder", text: $text)
       .keyboardType(.default)
       .introspectTextField { (textField) in
           let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: textField.frame.size.width, height: 44))
           let flexButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
           let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(textField.doneButtonTapped(button:)))
           doneButton.tintColor = .systemPink
           toolBar.items = [flexButton, doneButton]
           toolBar.setItems([flexButton, doneButton], animated: true)
           textField.inputAccessoryView = toolBar
        }
}

extension  UITextField {
   @objc func doneButtonTapped(button:UIBarButtonItem) -> Void {
      self.resignFirstResponder()
   }
}

输出


2
谢谢。我不是第三方框架的忠实粉丝,因为我曾经遇到过很多次Xcode更新后破坏了它们,最终只能自己重新编码的情况。但愿你的研究能帮助其他人! - JohnSF
我通常会避免使用第三方框架,但现在我已经了解了Introspect,它是一个非常棒的概念,苹果公司应该明智地将其集成。最终,它可能会变得多余,但在SwiftUI达到那种成熟水平之前,Introspect将是一个巨大的帮助。 - Robert Schmid

9
如果使用@Binding存在问题,可以实现符合UITextFieldDelegate协议的Coordinator类来解决。如果需要的话,这可以进一步定制TextField。
struct DecimalTextField: UIViewRepresentable {
     private var placeholder: String
     @Binding var text: String

     init(_ placeholder: String, text: Binding<String>) {
        self.placeholder = placeholder
        self._text = text
     } 

     func makeUIView(context: Context) -> UITextField {
        let textfield = UITextField()
        textfield.keyboardType = .decimalPad
        textfield.delegate = context.coordinator
        textfield.placeholder = placeholder
        let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: textfield.frame.size.width, height: 44))
        let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(textfield.doneButtonTapped(button:)))
        toolBar.items = [doneButton]
        toolBar.setItems([doneButton], animated: true)
        textfield.inputAccessoryView = toolBar
        return textfield
     }

     func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
     }

     func makeCoordinator() -> Coordinator {
        Coordinator(self)
     }

     class Coordinator: NSObject, UITextFieldDelegate {
        var parent: DecimalTextField
    
     init(_ textField: DecimalTextField) {
        self.parent = textField
     }
    
     func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
         if let currentValue = textField.text as NSString? {
             let proposedValue = currentValue.replacingCharacters(in: range, with: string) as String
             self.parent.text = proposedValue
         }
         return true
     }
   }
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接