0

So I am trying to build a custom look to a Text Input in SwiftUI.

I have attempting to do this with a few different tricks, and the one I came up with was using labels and i was going to hide a TextInput behind the labels and let it basically act like a textinput without actually being a text input.

I dont even know if this is possible. If not I need to go back to the drawing board.

This is what I want it to look like blank Black numbers, with the custom spacing and font etc.

enter image description here

Once numbers start to be entered they turn white.

however as you can see, the textInput is visible. Is it possible to hide it?

enter image description here

import SwiftUI

struct CustomInput: View {
    
    @State var id: String = " "
    @State var label1: Character = Character(" ")
    @State var label2: Character = Character(" ")
    @State var label3: Character = Character(" ")
    @State var label4: Character = Character(" ")
    @State var label5: Character = Character(" ")
    @State var label6: Character = Character(" ")
    @State var label7: Character = Character(" ")
    @State var label8: Character = Character(" ")
    @State var label9: Character = Character(" ")
    
   
    
    var body: some View {

        ZStack {
            Color.green
            HStack(spacing: 15){
                ForEach(0 ..< 9) { index in
                    Text(String(id[safe: index] ?? "0"))
                        .font(.custom("regular", size: 32))
                        .frame(height: 48)
                        .foregroundColor(id.count <= index ? .black : .white)
                }
            }
            .frame(width: 311, height: 48)
            
            TextField("", text: $id)
                .frame(width: 311, height: 48)
          
        }
        .frame(width: 311, height: 48)

            
    }
    
}

extension StringProtocol {
    subscript(safe offset: Int) -> Character? {
        guard 0 ..< count ~= offset else {
            return nil
        }
        return self[index(startIndex, offsetBy: offset)]
    }
}
Bnd10706
  • 1,933
  • 5
  • 24
  • 39
  • Can you explain your case? For example is it always a set number of digits? – xTwisteDx Dec 28 '21 at 17:16
  • ya this is for a 9 digit number all the time. – Bnd10706 Dec 28 '21 at 17:18
  • @Bnd10706 Btw, you no longer need all those `label*`s – George Dec 28 '21 at 17:27
  • I tried without the labels, The custom font and custom spacing made it so i could not use a general Text Input to achieve the results., especially since i needed the 0's to be there all the time. This was just a trick i was trying to pull lol – Bnd10706 Dec 28 '21 at 17:30
  • @Bnd10706 I meant all the `@State` properties, you aren't using them – George Dec 28 '21 at 17:31
  • @Bnd10706 Also what's your deployment target? In iOS 15 there might be a nice way to solve this – George Dec 28 '21 at 17:31
  • all our devices are on iOS15 – Bnd10706 Dec 28 '21 at 17:32
  • I think. you are trying to do this https://stackoverflow.com/questions/57188098/swiftui-is-it-possible-to-automatically-move-to-the-next-textfield-after-1-char/70294937#70294937 – lorem ipsum Dec 28 '21 at 17:36
  • Here is another one that has an animation for [each character](https://stackoverflow.com/questions/70083022/swiftui-how-to-animate-each-character-in-textfield/70087100#70087100) – lorem ipsum Dec 28 '21 at 17:38

1 Answers1

1

Here is a workable solution for you to continue off of. Honestly your best bet is to probably use a UIViewRepresentable but I'll leave that up to you.

NOTE: You'll need to handle a few things here. It'll need a few possible errors to be handled.

  • Bug if you attempt to use a character, non-digit.

  • Disable Context Menu actions (Copy, Paste, etc..) I hid it by setting accent to .clear

  • Add your styling.

    struct FirstView: View {
      @State var numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0]
      @State var editedNumbers = ""
    
      var body: some View {
          ZStack {
              HStack {
                  ForEach(0..<numbers.count) { index in
                      Text(String(numbers[index]))
                          .padding(.horizontal, 2)
                          .foregroundColor(numbers[index] != 0 ? .white : .black)
                  }
              }
                  .frame(width: UIScreen.main.bounds.width * 0.8)
                  .padding(.vertical)
                  .background(Color.green)
    
    
              TextField("", text: $editedNumbers)
                  .frame(width: UIScreen.main.bounds.width * 0.8)
                  .padding(.vertical)
                  .foregroundColor(.clear)
                  .accentColor(.clear)
                  .onChange(of: editedNumbers, perform: { value in
    
                  numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0]
    
                  for (index, char) in editedNumbers.enumerated() {
                      if index >= 0 && index <= 8 {
                          numbers[index] = char.wholeNumberValue ?? 0
                      }
                  }
              })
          }
      }
    }
    
xTwisteDx
  • 2,152
  • 1
  • 9
  • 25
  • Effect wise, this basically is what im looking for. It wont allow for delete, but thank you for this! i can mess around and see if I can figure out the errors – Bnd10706 Dec 28 '21 at 17:52
  • @Bnd10706 most of you errors/bugs that will come from this solution can be handled in that `.onChange` modifier. I don't know what your requirements are so that's why I left them out. – xTwisteDx Dec 28 '21 at 17:53
  • Basically this is a 9 digit tag number that needs to be entered during this flow. The UI designers came up with this stupid design and its something everyone likes. Its dumb. Thank you, this is a great starting off point. – Bnd10706 Dec 28 '21 at 17:58
  • actually, you kinda solved my problem with just doing the .forgroundcolor to clear. I can work with this off my original code – Bnd10706 Dec 28 '21 at 18:02
  • Just added a fix for a few things there to stop the crashing as well as resetting back to 0 on backspace. – xTwisteDx Dec 28 '21 at 18:03