3

Using iOS13.2, Swift-5.1.2, Xcode-11.2, I try the following:

I want to use a TextField. The user shall only be able to enter x-amount of characters into the TextField.

My code looks as follows:

import Combine
import SwiftUI

class Entry: ObservableObject {

  @Published var entry = "" {
    didSet {
        entry = String(entry.prefix(6)) // trying to limit to 6 characters
    }
  }
}

And in the above code, there is already the exception line.

I can see that the didSet{...} is wrong (since we end up in an endless loop setting/didSetting again and again)...

What is a better way to limit a TextField to x-amount of characters ?

Here is the rest of the code:

struct NumberView: View {
    var body: some View {

        Group {
            HStack {
                Spacer()
                NumberIcon(number: 1)
                NumberIcon(number: 2)
                NumberIcon(number: 3)
                NumberIcon(number: 4)
                NumberIcon(number: 5)
                NumberIcon(number: 6)
                Spacer()
            }
         }
    }
}
struct NumberIcon: View {
    @ObservedObject private var entry = Entry()
    var number: Int = 0
    var body: some View {
        TextField(" ", text: $entry.entry, onEditingChanged: { editing in
            print(editing)
            print(self.$entry)
        })
            .padding()
            .foregroundColor(Color.black)
            .background(Color.green)
            .font(.largeTitle)
            .lineLimit(1)
            .cornerRadius(16.0)
            .clipped()
            .keyboardType(.numberPad)
    }
}

struct NumberView_Previews: PreviewProvider {
    static var previews: some View {
        NumberView()
    }
}

I know that there are UIKit wrapper possibilities to use the good-old shouldChangeCharactersIn delegate methods form UITextFieldDelegate - but I would like to implement the character limitation purely with SwiftUI (no UIKit code). How can I do that ?

iKK
  • 6,394
  • 10
  • 58
  • 131

4 Answers4

4

Thanks to this answer here, I found a solution:

The Entry class can be rewritten:

class Entry: ObservableObject {

    let characterLimit = 6   // limiting to 6 characters
    @Published var entry = "" {
        didSet {
            if entry.count > characterLimit && oldValue.count <= characterLimit {
                entry = oldValue
            }
        }
    }
}
iKK
  • 6,394
  • 10
  • 58
  • 131
2

U can use this:

.onReceive(variable.publisher.collect()) {
        variable = String($0.prefix(4))
}
adri567
  • 533
  • 5
  • 20
  • This works, but in my case, I had to add `if variable.count > 4 { ...... prefix(4)` because otherwise if your variable is empty it causes a crash. – oğuz Jul 14 '23 at 10:21
1

The easiest way to achive this in SwiftUI is just to disable the TextField when the maximum number of characters is reached:

struct LimittedTextField: View {

    @State private var entry = ""

    let characterLimit = 6

    var body: some View {
        TextField("", text: $entry)
            .disabled(entry.count > (characterLimit - 1))
    }
}
LuLuGaGa
  • 13,089
  • 6
  • 49
  • 57
  • Thank you LuLuGaGa, I like that solution. However, it makes the keyboard disappear. And my final solution looks for a way to jump to a second TextField when the user types x-amount of character in the first TextField. I have a stackoverflow-question to this respect here: [LINK](https://stackoverflow.com/q/58673159/3826232). Maybe you have a solution to this question as well - I appreciate it ! – iKK Nov 02 '19 at 16:44
  • 1
    This one works well and is very easy to implement! You can also add something like this to have a counter near the field: Text("\(feedback.count) of 256") – dbDev Jul 08 '22 at 06:20
  • The main disadvantage of this approach is that if you reach the limit, the TextField is disabled and you are not able to correct any mistakes you have made - like typos, etc. – Petr Holub Aug 16 '23 at 16:33
1

I've written a library for these kinds of use cases:

import DataField

struct ContentView: View {

    @State private var text = ""
    private let characterLimit = 6

    var body: some View {
        DataField("My Field", data: $text) { text in text.count < 6 } 
    }
}
Marcus Rossel
  • 3,196
  • 1
  • 26
  • 41