Home > front end >  Creating a custom textfield
Creating a custom textfield

Time:07-02

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.

enter image description here

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.

  • Related