Быстрое скрытие текстового поля с помощью firstResponder ()

#swift #swiftui

#swift #swiftui

Вопрос:

Новичок здесь 😬

Я пытаюсь сделать «ввод» в SwiftUI с различными размерами шрифта.

Из того, что я могу сказать из других сообщений, нет способа сделать это с помощью собственного текстового поля, поэтому вместо этого я создал HStack с splicedText и отрисовал каждую часть моего текста отдельно для достижения желаемого результата. Этот бит работает нормально.

Однако теперь у меня есть как обычное текстовое поле, так и моя «метка», и я не могу понять, как скрыть это текстовое поле и при этом сохранить .decimalPad его открытым.

Это тот вид, которого я пытаюсь достичь:

введите описание изображения здесь

Это то, что у меня есть до сих пор:

введите описание изображения здесь

Как вы можете видеть, мое значение отображается дважды. один почти правильно отформатирован, а другой неформатирован ниже (текстовое поле).

Я попытался добавить a .hidden() в текстовое поле, и хотя это скрывает текстовое поле (), мой firstResponder() .decimalPad больше не отображается, и у меня нет способа ввести свое значение: (

Я не хочу просто устанавливать для него значение opacity(0) либо, поскольку я не хочу, чтобы оно занимало какое-либо место на мой взгляд.

В конечном итоге на этом экране будет 2 входа, и мне нужно иметь возможность щелкнуть «ярлык», чтобы .decimalPad снова открыть. Однако я еще не зашел так далеко.

Вот код для отображения этого:

 import SwiftUI
import SwiftUIX

struct CreateEntryView : View {
    
    class ViewModel: ObservableObject {
        @Published var value = "$17.00" {
            didSet {
                let validCharSet = CharacterSet(charactersIn: "$1234567890.,")
                
                print("value: "   value)
                // add "$" prefix
                if value.prefix(1) != "$" { value = "$"   value  }
                
                //check if the new string contains any invalid characters
                if (value).rangeOfCharacter(from: validCharSet.inverted) != nil {
                    // remove any invalid characters
                    value = String(value.unicodeScalars.filter {
                        validCharSet.contains($0)
                    })
                }
                
                // replace ',' with '.' (required to handle all locales w/ decimalPad)
                let test = value.replacingOccurrences(of: ",", with: ".")
                print("replace comma: "   test)
                
                
                // check if there's more than 2 decimals
                // check if there's more than 1 dot
                // check if there's more than 1 dollar-sign
                
                
            }
        }
    }
    
    @ObservedObject var viewModel = ViewModel()
    
    // create splicedLabel for making label with varyign fontSize
    var splicedLabel: [String] {
        var spliceString = viewModel.value
        // remove $ from string
        spliceString.remove(at: spliceString.startIndex)
        return spliceString.split(separator: ".").map(String.init)
    }
    
    
    var body: some View {
        VStack {
            HStack(alignment: .bottom, spacing: 0) {
                Text("$")
                    .font(.body)
                    .fontWeight(.bold)
                    .padding(.bottom, 5)
                if self.splicedLabel.indices.contains(0) {
                    Text(self.splicedLabel[0])
                        .font(.largeTitle)
                        .fontWeight(.bold)
                }
                if self.splicedLabel.indices.contains(1) {
                    HStack(alignment: .bottom, spacing: 0) {
                        Text(".")
                            .font(.body)
                            .fontWeight(.bold)
                        Text(self.splicedLabel[1])
                            .font(.body)
                            .fontWeight(.bold)
                    }.padding(.bottom, 5)
                }
            }
            
    
            CocoaTextField("$123", text: $viewModel.value)
                .isFirstResponder(true) // autoFocus
                .keyboardType(.decimalPad)
            
        }
    }
}
 

Был бы очень признателен за информацию о том, как этого добиться. Возможно, я ошибаюсь, пытаясь использовать тексты в HStack для разных размеров шрифта.

Комментарии:

1. Возможно, используйте ZStack и alpha zero, чтобы исключить его из потока просмотра

Ответ №1:

Возможно, вы можете сделать это, используя UITextField и NSAttributedString, которые могут быть украшены шрифтами разного размера или цвета.

 import UIKit
import SwiftUI
import Combine

struct MyTextField: UIViewRepresentable {
  private var placeholder: String
  @Binding private var text: String
  private var textField = UITextField()

  init(_ placeholder: String, text: Binding<String>) {
    self.placeholder = placeholder
    self._text = text
  }

  func makeCoordinator() -> Coordinator {
    Coordinator(textField: self.textField, text: self._text)
  }

  func makeUIView(context: Context) -> UITextField {
    textField.placeholder = self.placeholder
    textField.font = UIFont.boldSystemFont(ofSize: 17)
    textField.keyboardType = .decimalPad
    textField.text = self.text
    return textField
  }

  func updateUIView(_ uiView: UITextField, context: Context) {
  }

  class Coordinator: NSObject {
    private var dispose = Set<AnyCancellable>()
    @Binding var text: String

    init(textField: UITextField, text: Binding<String>) {
      self._text = text
      super.init()

      let publisher = NotificationCenter.default
        .publisher(for: UITextField.textDidChangeNotification, object: textField)
        .compactMap { $0.object as? UITextField }
        .share()
        
      publisher.compactMap { $0.text }
        .receive(on: RunLoop.main)
        .assign(to: .text, on: self)
        .store(in: amp;dispose)
      
      publisher.receive(on: RunLoop.main)
        .sink(receiveValue: { (textField: UITextField) in
          Coordinator.setAttributedString(textField: textField)
        })
        .store(in: amp;dispose)
    }
    
    static func setAttributedString(textField: UITextField) {
      guard var text = textField.attributedText?.string else { return }
      text = text.prefix(1) == "$" ? text : "$"   text
      
      let dollarAttributes: [NSAttributedString.Key: Any] = [
        .foregroundColor: UIColor.black,
        .font: UIFont.boldSystemFont(ofSize: 34)
      ]
      let otherAttributes: [NSAttributedString.Key: Any] = [
        .foregroundColor: UIColor.black,
        .font: UIFont.boldSystemFont(ofSize: 17)
      ]
      
      let attributedText = NSMutableAttributedString(string: text)
      attributedText.addAttributes(dollarAttributes, range: NSRange(location: 1, length: text.count - 1))
      
      let strings = text.split(separator: ".")
      if let dollar = strings.first, strings.count > 1 {
        let range = NSRange(location: dollar.count, length: text.count - dollar.count)
        attributedText.addAttributes(otherAttributes, range: range)
      }
      textField.attributedText = attributedText
    }
  }
}

extension MyTextField.Coordinator: UITextFieldDelegate {
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
  }
}

struct ContentView: View {
  @State var text: String = "$"

  var body: some View {
    VStack {
      MyTextField("placeholder", text: self.$text).padding()
      Text(self.text).foregroundColor(.red).padding()
    }
  }
}