TextView component in swiftUI || adjustable height textField || Dynamic height textField in SwiftUI || TextField height change at runtime

Solution:

//# MARK: - TextView Component


fileprivate struct UITextViewAdjustable: UIViewRepresentable {

    typealias UIViewType = UITextView


    @Binding var text: String

    @Binding var calHeight: CGFloat

    var onDone: (() -> Void)?


    func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {

        let textField = UITextView()

        textField.delegate = context.coordinator

        textField.isEditable = true

        textField.font = UIFont.preferredFont(forTextStyle: .body)

        textField.isSelectable = true

        textField.isUserInteractionEnabled = true

        textField.isScrollEnabled = false

        textField.backgroundColor = UIColor.clear

        if nil != onDone {

            textField.returnKeyType = .done

        }

        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

        return textField

    }


    func updateUI(_ uiView: UITextView, context: UIViewRepresentableContext< UITextViewAdjustable >) {

        if uiView.text != self.text {

            uiView.text = self.text

        }

        if uiView.window != nil, !uiView.isFirstResponder {

            uiView.becomeFirstResponder()

        }

        UITextViewAdjustable.adjustcalculateHeight(view: uiView, result: $calHeight)

    }


    fileprivate static func adjustcalculateHeight(view: UIView, result: Binding<CGFloat>) {

        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))

        if result.wrappedValue != newSize.height {

            DispatchQueue.main.async {

                result.wrappedValue = newSize.height // !! must be called asynchronously

            }

        }

    }


    func makeCoordinator() -> Coordinator {

        return Coordinator(text: $text, height: $calHeight, onDone: onDone)

    }


    final class Coordinator: NSObject, UITextViewDelegate {

        var text: Binding<String>

        var calHeight: Binding<CGFloat>

        var onDone: (() -> Void)?


        init(text: Binding<String>, height: Binding<CGFloat>, onDone: (() -> Void)? = nil) {

            self.text = text

            self. calHeight = height

            self.onDone = onDone

        }


        func textViewDidChange(_ uiView: UITextView) {

            text.wrappedValue = uiView.text

            UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)

        }


        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

            UITextViewAdjustable.adjustcalculateHeight(view: textView, result: calculatedHeight)

            return true

        }

    }


}


struct adjustTextField: View {


    private var placeholder: String

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


    @Binding private var text: String

    private var internalText: Binding<String> {

        Binding<String>(get: { self.text } ) {

            self.text = $0

            self.showingPlaceholder = $0.isEmpty

        }

    }


    @State private var dynamicHeight: CGFloat = 100

    @State private var showingPlaceholder = false


    init (_ placeholder: String = "", text: Binding<String>, onCommit: (() -> Void)? = nil) {

        self.placeholder = placeholder

        self.onCommit = onCommit

        self._text = text

        self._showingPlaceholder = State<Bool>(initialValue: self.text.isEmpty)

    }


    var body: some View {

        UITextViewAdjustable(text: self.internalText, calHeight: $dynamicHeight, onDone: onCommit)

            .frame(minHeight: dynamicHeight, maxHeight: dynamicHeight)

            .background(placeholderView, alignment: .topLeading)

    }


    var placeholderView: some View {

        Group {

            if showingPlaceholder {

                Text(placeholder).foregroundColor(.gray)

                    .padding(.leading, 4)

                    .padding(.top, 8)

            }

        }

    }

}



Use the above code inside your contentView...Call the dynamic height

textView using the below code.





             var test:String = ""

             let testBinding = Binding<String>(get: { test }, set: {

                    test = $0 } )


            return AnyView(adjustTextField("Enter some text here", text: testBinding, onCommit: {

                print("Final text: \(test)")

            }))


Comments

Popular posts from this blog

How to blink a button iOS swift?

iOS flutter while run to iPhone it had the error - Cannot find type 'PhoneNumberKit' in scope