拼写改为确认按钮手动提交,点击已填字母可删除重输
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import UIKit
|
||||
|
||||
final class SpellingInputView: UIView, UITextFieldDelegate {
|
||||
var onComplete: ((String) -> Void)?
|
||||
var onChange: (() -> Void)?
|
||||
|
||||
private let hiddenField = UITextField()
|
||||
private let stack = UIStackView()
|
||||
@@ -9,16 +9,21 @@ final class SpellingInputView: UIView, UITextFieldDelegate {
|
||||
private var chars: [String] = []
|
||||
private var blankPositions: [Int] = []
|
||||
private var filledBlanks: [String] = []
|
||||
private var currentBlankIndex = 0
|
||||
private var cursorIndex = 0
|
||||
private var boxViews: [UIView] = []
|
||||
private var cursorView: UIView?
|
||||
private var inputEnabled = true
|
||||
|
||||
var isComplete: Bool {
|
||||
!filledBlanks.isEmpty && filledBlanks.allSatisfy { !$0.isEmpty }
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
stack.axis = .horizontal
|
||||
stack.spacing = 6
|
||||
stack.alignment = .center
|
||||
stack.isUserInteractionEnabled = true
|
||||
addSubview(stack)
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
@@ -47,11 +52,19 @@ final class SpellingInputView: UIView, UITextFieldDelegate {
|
||||
chars = blankedWord.map { String($0) }
|
||||
self.blankPositions = blankPositions
|
||||
filledBlanks = Array(repeating: "", count: blankPositions.count)
|
||||
currentBlankIndex = 0
|
||||
cursorIndex = 0
|
||||
inputEnabled = true
|
||||
rebuildBoxes()
|
||||
}
|
||||
|
||||
func typedWord() -> String {
|
||||
var rebuilt = chars
|
||||
for (i, pos) in blankPositions.enumerated() {
|
||||
rebuilt[pos] = filledBlanks[i]
|
||||
}
|
||||
return rebuilt.joined().lowercased()
|
||||
}
|
||||
|
||||
func activate() {
|
||||
guard inputEnabled, !hiddenField.isFirstResponder else { return }
|
||||
hiddenField.becomeFirstResponder()
|
||||
@@ -72,22 +85,17 @@ final class SpellingInputView: UIView, UITextFieldDelegate {
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
guard inputEnabled else { return false }
|
||||
if string.isEmpty {
|
||||
if currentBlankIndex > 0 {
|
||||
currentBlankIndex -= 1
|
||||
filledBlanks[currentBlankIndex] = ""
|
||||
if cursorIndex > 0 {
|
||||
cursorIndex -= 1
|
||||
filledBlanks[cursorIndex] = ""
|
||||
refreshBoxes()
|
||||
onChange?()
|
||||
}
|
||||
} else if let char = string.last, char.isLetter, currentBlankIndex < blankPositions.count {
|
||||
filledBlanks[currentBlankIndex] = String(char).lowercased()
|
||||
currentBlankIndex += 1
|
||||
} else if let char = string.last, char.isLetter, cursorIndex < blankPositions.count {
|
||||
filledBlanks[cursorIndex] = String(char).lowercased()
|
||||
cursorIndex += 1
|
||||
refreshBoxes()
|
||||
if currentBlankIndex == blankPositions.count {
|
||||
var rebuilt = chars
|
||||
for (i, pos) in blankPositions.enumerated() {
|
||||
rebuilt[pos] = filledBlanks[i]
|
||||
}
|
||||
onComplete?(rebuilt.joined().lowercased())
|
||||
}
|
||||
onChange?()
|
||||
}
|
||||
textField.text = nil
|
||||
return false
|
||||
@@ -99,10 +107,12 @@ final class SpellingInputView: UIView, UITextFieldDelegate {
|
||||
stack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
boxViews = chars.enumerated().map { index, ch in
|
||||
let box = makeBox()
|
||||
if ch != "_", let label = box.subviews.compactMap({ $0 as? UILabel }).first {
|
||||
box.tag = index
|
||||
if ch != "_", let label = box.viewWithTag(100) as? UILabel {
|
||||
label.text = ch.uppercased()
|
||||
label.isHidden = false
|
||||
}
|
||||
box.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(boxTapped(_:))))
|
||||
stack.addArrangedSubview(box)
|
||||
NSLayoutConstraint.activate([
|
||||
box.widthAnchor.constraint(equalToConstant: 36),
|
||||
@@ -113,6 +123,20 @@ final class SpellingInputView: UIView, UITextFieldDelegate {
|
||||
refreshBoxes()
|
||||
}
|
||||
|
||||
@objc private func boxTapped(_ gesture: UITapGestureRecognizer) {
|
||||
guard inputEnabled, let box = gesture.view else { return }
|
||||
let index = box.tag
|
||||
guard chars[index] == "_", let blankIndex = blankPositions.firstIndex(of: index) else {
|
||||
activate()
|
||||
return
|
||||
}
|
||||
filledBlanks[blankIndex] = ""
|
||||
cursorIndex = blankIndex
|
||||
refreshBoxes()
|
||||
onChange?()
|
||||
activate()
|
||||
}
|
||||
|
||||
private func makeBox() -> UIView {
|
||||
let box = UIView()
|
||||
box.backgroundColor = .quaternarySystemFill.withAlphaComponent(0.15)
|
||||
@@ -147,13 +171,14 @@ final class SpellingInputView: UIView, UITextFieldDelegate {
|
||||
label?.text = filled.uppercased()
|
||||
label?.isHidden = filled.isEmpty
|
||||
|
||||
let isCurrent = inputEnabled && blankIndex == currentBlankIndex
|
||||
let isCurrent = inputEnabled && blankIndex == cursorIndex
|
||||
box.layer.borderColor = isCurrent ? UIColor.tintColor.cgColor : UIColor.separator.cgColor
|
||||
box.layer.borderWidth = isCurrent ? 2 : 1
|
||||
|
||||
if isCurrent, filled.isEmpty {
|
||||
let cursor = UIView()
|
||||
cursor.backgroundColor = .tintColor
|
||||
cursor.isUserInteractionEnabled = false
|
||||
box.addSubview(cursor)
|
||||
cursor.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
|
||||
Reference in New Issue
Block a user