我正在尝试在 SwiftUI 中开发一个填字游戏应用程序。我想总是显示键盘,并且能够选择一个正方形并让按键在那里输入一个字母。有没有办法始终在视图中显示键盘?如果我使用 TextField,我可以让键盘显示出来,但是让拼图的每个单元格成为一个文本字段似乎很笨拙。提前致谢。
答案 0 :(得分:1)
最终产品:https://imgur.com/a/Q85GOWj
我决定删除我的其他答案,并尝试练习代码。首先,您需要创建一个始终作为第一响应者的 UITextField。你可以这样做:
struct PermanentKeyboard: UIViewRepresentable {
@Binding var text: String
class Coordinator: NSObject, UITextFieldDelegate {
var parent: PermanentKeyboard
init(_ parent: PermanentKeyboard) {
self.parent = parent
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Async to prevent updating state during view update
DispatchQueue.main.async {
if let last = string.last {
//Check if last character is alpha
if last.isLetter {
//Changes the binding to the last character of input, UPPERCASED
self.parent.text = String(last).uppercased()
}
//Allows backspace
} else if string == "" {
self.parent.text = ""
}
}
return false
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextField {
let textfield = UITextField()
textfield.delegate = context.coordinator
//Makes textfield invisible
textfield.tintColor = .clear
textfield.textColor = .clear
return textfield
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
//Makes keyboard permanent
if !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
//Reduces space textfield takes up as much as possible
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
}
}
这被包装在一个 UIViewRepresentable 中,以便我们可以在我们的 SwiftUI 视图中使用它。当我们的视图可见并且它的键盘永远不会被关闭时,此文本字段将自动聚焦。
现在我们必须使用实际的填字游戏制作我们的 SwiftUI 视图。首先,我将在 ContentView 中定义一个结构体,用作填字游戏框:
struct CharacterBox: View {
@Binding var character: String
@Binding var selectedInput: Int
let index: Int
var isSelected: Bool {
index == selectedInput
}
var body: some View {
ZStack {
//Required for tap gesture to work
Rectangle()
.fill(Color.white)
//To signify selection
Rectangle()
.stroke(isSelected ? Color.orange : Color.black)
Text(character)
.foregroundColor(.black)
}
.frame(width: 50, height: 50)
.onTapGesture {
selectedInput = index
}
}
}
这个 CharacterBox 结构可以绑定到我们 ContentView 中的状态变量字符串,如果它与选定的输入变量匹配,那么它也会被不可见的文本字段修改。
到内容视图。我们需要为每个 CharacterBox 绑定一个状态变量,但显然我们不能手动完成。因此,我们将创建一个数组,并让每个 CharacterBox 根据其索引绑定到一个字符串。
在此之前,我已经创建了这个函数来帮助我们用一些空字符串初始化一个数组。
func createEmptyStringArray(count: Int) -> [String] {
var array = [String]()
for _ in 0..<count {
array.append("")
}
return array
}
如果你愿意,你可以把它放在 ContentView 之外;它还可以让您稍后重用它。然后我们将要绑定的输入定义为这样的数组:
@State private var inputs = createEmptyStringArray(count: 3)
@State private var selectedInput = 0
selectedInput 是输入数组中不可见文本字段将绑定到的字符串的索引。为了给每个 CharacterBox 绑定,我们可以把这个函数放在我们的 ContentView 中:
func getInputBinding(index: Int) -> Binding<String> {
Binding<String>(
get: {
inputs[index]
},
set: {
inputs[index] = $0
}
)
}
请注意,因为您定义的每个 CharacterBox 的索引不应更改,因此可以使用函数来获取这样的 Binding。然而,对于我们的 selectedIndex,我们需要它在每次 selectedIndex 改变时更新绑定,所以我们必须改用计算属性。像这样定义它:
var selectedInputBinding: Binding<String> {
Binding<String>(
get: {
inputs[selectedInput]
},
set: {
inputs[selectedInput] = $0
}
)
}
最后,把它们放在一起,这就是我们的身体:
var body: some View {
ZStack {
PermanentKeyboard(text: selectedInputBinding)
//Hides textfield and prevents user from selecting/pasting/copying
Rectangle()
.fill(Color.white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
//Your crossword with bindings to inputs here; this is hard coded/manually created
VStack(spacing: 1) {
ForEach(0 ..< inputs.count) { index in
CharacterBox(character: getInputBinding(index: index), selectedInput: $selectedInput, index: index)
}
}
}
}