2

I created my own TextField with the following tutorial:https://www.youtube.com/watch?v=9kfbJVqqTKc&t=17s. Every things works fine but I added a feature to refresh the content of the TextField. The problem is that when I set the content programmatically, the function textViewDidChange() is not called in the coordinator. This causes the whole TextField to collapse, which means that the placeholder no longer disappears when I type something and it doesn't automatically get bigger. When you get to the point where a new line should be created, the text is simply deleted for whatever reason.

Is there a way to fix this problem?

Code:

import SwiftUI

struct CustomTextField: View {
var title: String
@Binding var text: String
var isFocused: Binding<Bool>?

@State var height: CGFloat = 20

var onCommit: (() -> ())?

init(_ title: String, text: Binding <String>, isFocused: Binding<Bool>? = nil, onCommit: (() -> ())? = nil){
    self.title = title
    self._text = text
    self.isFocused = isFocused
    self.onCommit = onCommit
}
var body: some View {
    ZStack(alignment: .topLeading){
        Text(title)
            .foregroundColor(Color.secondary.opacity(text.isEmpty ? 0.5 : 0))
            .animation(nil)
        CustomTextFieldRep(text: $text, isFocused: isFocused, height: $height, onCommit: onCommit)
            .frame(height: height)
    }
}
}

struct CustomTextFieldRep: UIViewRepresentable{

@Binding var text: String
var isFocused: Binding<Bool>?
@Binding var height: CGFloat
var onCommit: (() -> ())?

func makeUIView(context: Context) -> UITextView {
    let uiView = UITextView()
    
    uiView.font = UIFont.preferredFont(forTextStyle: .body)
    uiView.backgroundColor = .clear
    uiView.text = text
    uiView.delegate = context.coordinator
    uiView.textContainerInset = .zero
    uiView.textContainer.lineFragmentPadding = 0
    
    return uiView
}

func updateUIView(_ uiView: UITextView, context: Context) {
    if uiView.text != text{
        uiView.text = text
        
        let size = uiView.sizeThatFits(CGSize(width: uiView.frame.width, height: CGFloat.greatestFiniteMagnitude))
        height = size.height
    }
    
    if let isFocused = isFocused?.wrappedValue{
        if isFocused && !uiView.isFirstResponder{
            uiView.becomeFirstResponder()
        }else if !isFocused && uiView.isFirstResponder{
            uiView.resignFirstResponder()
        }
    }
}

func makeCoordinator() -> Coordinator {
    Coordinator(rep: self)
}

class Coordinator: NSObject, UITextViewDelegate{
    
    var rep: CustomTextFieldRep
    
    internal init(rep: CustomTextFieldRep){
        self.rep = rep
    }
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if let onCommit = rep.onCommit, text == "\n"{
            onCommit()
            return false
        }
        
        return true
    }
    
    func textViewDidChange(_ textView: UITextView) {
        rep.text = textView.text
        
        let size = textView.sizeThatFits(CGSize(width: textView.frame.width, height: CGFloat.greatestFiniteMagnitude))
        rep.height = size.height
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
        rep.isFocused?.wrappedValue = true
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        rep.isFocused?.wrappedValue = false
    }
}
}

I think the problem can be fixed if I call the textViewDidChange() function in the coordinator itself. However, I don't know how to do that.

Thanks in advance

EDIT: How I implemented my custom TextField:

struct View: View{
@ObservedObject private var addUebungVM = AddÜbungViewModel()
enum Focus{
    case name, beschreibung
}

@State var focus: Focus? = .name

var nameFocusBinding: Binding<Bool>{
    $focus.isEqual(to: .name)
}

var beschreibungFocusBinding: Binding<Bool>{
    $focus.isEqual(to: .beschreibung)
}

var body: some View{
    CustomTextField(placeholderName, text: $addUebungVM.übungName, isFocused: nameFocusBinding, onCommit: saveNewÜbung)
      .alignmentNewExercise()
      .styleTextField()
}

private func saveNewÜbung(){
    if !addUebungVM.übungName.isEmpty{
        DispatchQueue.main.async{
            addUebungVM.übungName = " "
            addUebungVM.übungBeschreibung = " "
            placeholderName = NSLocalizedString("nächsteÜbungString", comment: "")
            focus = .name
        }
    }
}
}

EDIT 2: Thinks I found out:

When the view appears the first time:

-everything works great

After the first reset...

  1. When I call the second reset over the keyboard-button "return" it works when the text isn't longer than a normal line, but when I call the reset over a "normal" button it doesn't work at all
  2. the placeholder is no longer displayed
  3. when the text I type gets longer than a normal line and the text box should gets bigger, the text gets lost
Lukas
  • 251
  • 2
  • 13

1 Answers1

1

I am using your code and refreshing the content through the state variable as below:

struct ContentView: View {
    @State var text = ""
    @State var isFocused = true
    var body: some View {
        CustomTextField("Initial text", text: $text, isFocused: $isFocused) {
            print("done")
            let t = DispatchTime.now() + 2.0
            DispatchQueue.main.asyncAfter(deadline: t) {
                text = "Changed text"
            }
        }
    }
}

is this the solution to your problem?

Subha_26
  • 440
  • 4
  • 14
  • Hardcoding a delay isn't very good practice – aheze Jan 30 '21 at 16:25
  • @Subha_26 It works in a certain way. I can now press the reset button and also enter something again. However, if I have deleted the content once, then enter something again and go out of the text field, the content is automatically deleted. Do you have any idea why? – Lukas Jan 30 '21 at 16:45
  • @aheze This was done just to induce a programmatic change to the content of TextField. In reality this would be trigerred on some action or API response. – Subha_26 Jan 30 '21 at 16:46
  • One more thing: The placeholder is not visible again when I change the text programmatically. – Lukas Jan 30 '21 at 16:46
  • @Subha_26 ah I see – aheze Jan 30 '21 at 16:49
  • @Lukas Could you please provide the code you have written (The usage of CustomTextField)? – Subha_26 Jan 30 '21 at 16:52
  • Yes one second. – Lukas Jan 30 '21 at 16:53
  • Also I have still the problem that when the height should get bigger the content gets lost – Lukas Jan 30 '21 at 17:12
  • @Subha_26 I have added to the question some things that I have found out – Lukas Jan 30 '21 at 17:51