So I've been trying to create a custom textfield but I am at a lost. I have been stuck here for a very long time.
So I have 3 textfields when all of them are empty there is an overlay on the Stack. I put this on so f3,f2 can't be tapped. Now when the user taps on the overlay the f1 gets focused. Now this is where I have been stuck for a very long time.
Say the user types 123, what I like to happen is when the user types in 1 it stays on field1. Now when the user types in 2 the 1 goes to field2 and the 2 stays in field1 and so on. Now when the user types in 4 I would like to replace the 3 with the 4. Then the last thing I want to do is when field1 is empty then when the user presses the delete key again field2 text move back to field 1 and so on.
This is how far I got and haven't been able to proceed.
struct TV1: View {
@State var f1: String = ""
@State var f2: String = ""
@State var f3: String = ""
@FocusState var ff: Field?
func isFormEmpty() -> Bool {
return f1.isEmpty && f2.isEmpty && f3.isEmpty
}
var body: some View {
HStack {
TextField("3", text: $f3)
.font(.headline.weight(.semibold))
.keyboardType(.numberPad)
.frame(width: 44, height: 44)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
.focused($ff, equals: .f3)
.multilineTextAlignment(.center)
TextField("2", text: $f2)
.font(.headline.weight(.semibold))
.keyboardType(.numberPad)
.frame(width: 44, height: 44)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
.focused($ff, equals: .f2)
.multilineTextAlignment(.center)
.disabled(f1.isEmpty)
TextField("1", text: $f1)
.font(.headline.weight(.semibold))
.keyboardType(.numberPad)
.frame(width: 44, height: 44)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
.focused($ff, equals: .f1)
.multilineTextAlignment(.center)
.onChange(of: f1) { newValue in
f1 = String(f1.prefix(2))
if f1.count == 2 {
if f2.isEmpty {
f2 = f1.first?.description ?? ""
}
f1.removeFirst()
}
}
}
.overlay (
Color.primary
.opacity(isFormEmpty() && ff == nil ? 0.01 : 0)
.onTapGesture {
ff = .f1
}
)
}
}
CodePudding user response:
Carefully reading your question I think this is what you want to achieve.
Added comments in the code for changes.
struct ContentView: View {
@State var f1: String = ""
@State var f2: String = ""
@State var f3: String = ""
@FocusState var focused: Bool // changed to Bool as youre only intersted in focussing field 1
func isFormEmpty() -> Bool {
return f1.isEmpty && f2.isEmpty && f3.isEmpty
}
var body: some View {
HStack {
TextField("3", text: $f3)
.frame(width: 44, height: 44)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
.disabled(f1.isEmpty || f2.isEmpty)
TextField("2", text: $f2)
.frame(width: 44, height: 44)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
.disabled(f1.isEmpty)
TextField("1", text: $f1)
.frame(width: 44, height: 44)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
.focused($focused) // added focus
.onChange(of: f1) { newValue in
if f1.count == 1 { return } // first letter input > do nothing
if f1.count == 0 { // delete was pressed
f1 = f2.first?.description ?? ""
f2 = f3.first?.description ?? ""
f3 = ""
return
}
f1 = String(f1.prefix(2))
if f2.isEmpty {
f2 = f1.first?.description ?? ""
} else if f3.isEmpty {
f3 = f2.first?.description ?? ""
f2 = f1.first?.description ?? ""
}
f1.removeFirst()
}
}
// common modifiers can be here
.font(.headline.weight(.semibold))
.keyboardType(.numberPad)
.multilineTextAlignment(.center)
.overlay (
Color.primary
.opacity(isFormEmpty() && !focused ? 0.01 : 0)
.onTapGesture {
focused = true
}
)
}
}
CodePudding user response:
You can limit the number of characters being entered in a textField through the delegate function of textField i.e. shouldChangeCharactersIn() and once the user has entered a character, you can call becomeFirstResponder() on the next textField and so on.
CodePudding user response:
As far as I can tell based on what you have provided us, you are not setting the FocusState on the fields anywhere with the following modifier:
.focused($ff, equals: <#Value#>)
Try having a look at the documentation for the FocusState.