Does UITextField in UIViewRepresentable have a solution?

I've been trying to wrap a UITextField in UIViewRepresentable in order to do some customisation but this does not seem to work.

My main issue is that the frame of the UITextField is wrong. I even raised a TSI for this. It was confirmed to be a bug (I raised a bug report) but was told that the only work around is effectively to completely re-write my own Text Field using the basics. I cannot believe something as simple as this has not been solved yet.

Below is my minimum code to show the problem. I've tried a lot of things: adding InputView to a ScrollView; constraining the frame of InputView, etc. The problem is that when you keep on typing to fill the UITextField then it's frame gets a negative x-offset and it goes off the screen so that you can no longer see the cursor.

How do I fix this, or do I really have no choice but to re-write UITextField?

import SwiftUI

struct InputView: UIViewRepresentable {
  let fontName = "Arial"
  let fontSize: CGFloat = 32.0
  
  @Binding var text: String

  typealias UIViewType = UITextField
  
  func makeUIView(context: Context) -> UIViewType {
    // Setup text view:
    // ----------------
    let textView = UITextField()
    textView.delegate = context.coordinator

    // Creae a dummy view for the inputView (keyboard) so that the default
    // keyboard is not shown:
    let dummyView = UIView(frame: CGRect.zero)
    textView.inputView = dummyView
    
    return textView
  }
  
  func makeCoordinator() -> InputView.Coordinator {
    return Coordinator(self)
  }
  
  func updateUIView(_ textView: UIViewType, context: Context) {
    if textView.text != text {
      textView.text = text
    }
  }
  
  class Coordinator: NSObject, UITextFieldDelegate {
    var parent: InputView
    
    init(_ parent: InputView) {
      self.parent = parent
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
      if let currentValue = textField.text as NSString? {
        let proposedValue = currentValue.replacingCharacters(in: range, with: string) as String
        self.parent.text = proposedValue
      }
      return true
    }
  }
}

struct ContentView: View {
  @State private var text: String = "Type here"
  
  var body: some View {
    VStack {
      InputView(text: $text)
      
      Text(text)
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Accepted Reply

I found something that fixed my problem but I'm not sure it is the right way of doing things. Setting the horizontal content compression resistance priority to defaultLow made it scroll correctly. The makeUIView function below shows the full solution.

func makeUIView(context: Context) -> UIViewType {
    // Setup text view:
    // ----------------
    let textView = UITextField()
    textView.delegate = context.coordinator
    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

    // Creae a dummy view for the inputView (keyboard) so that the default
    // keyboard is not shown:
    let dummyView = UIView(frame: CGRect.zero)
    textView.inputView = dummyView
    
    return textView
  }

Replies

I found something that fixed my problem but I'm not sure it is the right way of doing things. Setting the horizontal content compression resistance priority to defaultLow made it scroll correctly. The makeUIView function below shows the full solution.

func makeUIView(context: Context) -> UIViewType {
    // Setup text view:
    // ----------------
    let textView = UITextField()
    textView.delegate = context.coordinator
    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

    // Creae a dummy view for the inputView (keyboard) so that the default
    // keyboard is not shown:
    let dummyView = UIView(frame: CGRect.zero)
    textView.inputView = dummyView
    
    return textView
  }