UITextView - Passen Sie die Größe basierend auf dem Inhalt in SwiftUI an

8

Ich versuche herauszufinden, wie die Größe von UITextView vom Inhalt in SwiftUI abhängig gemacht werden kann. Ich habe die UITextView UIViewRepresentablewie folgt verpackt :

struct TextView: UIViewRepresentable {

    @Binding var showActionSheet: Bool

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

    func makeUIView(context: Context) -> UITextView {

        let uiTextView = UITextView()
        uiTextView.delegate = context.coordinator

        uiTextView.font = UIFont(name: "HelveticaNeue", size: 15)
        uiTextView.isScrollEnabled = true
        uiTextView.isEditable = true
        uiTextView.isUserInteractionEnabled = true
        uiTextView.backgroundColor = UIColor(white: 0.0, alpha: 0.05)
        uiTextView.isEditable = false

        return uiTextView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.attributedText = prepareText(question: question)

        var frame = uiView.frame
        frame.size.height = uiView.contentSize.height
        uiView.frame = frame
    }

    func prepareText(text: string) -> NSMutableAttributedString {
        ...................
        return attributedText
    }

    class Coordinator : NSObject, UITextViewDelegate {

        var parent: TextView

        init(_ view: TextView) {
            self.parent = view
        }

        func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
            parent.showActionSheet = true
            return false
        }
    }
}

Außerdem habe ich versucht, die Frame-Größe in updateUIView basierend auf der Inhaltsgröße zu ändern, aber es hatte keine Auswirkungen. Ich nehme an, dass die Ansicht zu diesem Zeitpunkt noch nicht einmal ein Layout ist und der Rahmen an einer anderen Stelle überschrieben wird. Ich würde mich sehr freuen, wenn mich jemand in die richtige Richtung weisen könnte.

Adam
quelle

Antworten:

2

Ich konnte dies zum Funktionieren bringen, indem ich eine Variable in der Textansicht band, die von der Kapselungsansicht zum Festlegen der Rahmenhöhe verwendet wird. Hier ist die minimale TextView-Implementierung:

struct TextView: UIViewRepresentable {

    @Binding var text: String?
    @Binding var attributedText: NSAttributedString?
    @Binding var desiredHeight: CGFloat

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

    func makeUIView(context: Context) -> UITextView {

        let uiTextView = UITextView()
        uiTextView.delegate = context.coordinator

        // Configure text view as desired...
        uiTextView.font = UIFont(name: "HelveticaNeue", size: 15)

        return uiTextView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        if self.attributedText != nil {
            uiView.attributedText = self.attributedText
        } else {
            uiView.text = self.attributedText
        }

        // Compute the desired height for the content
        let fixedWidth = uiView.frame.size.width
        let newSize = uiView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))

        DispatchQueue.main.async {
            self.desiredHeight = newSize.height
        }
    }

    class Coordinator : NSObject, UITextViewDelegate {

        var parent: TextView

        init(_ view: TextView) {
            self.parent = view
        }

        func textViewDidEndEditing(_ textView: UITextView) {
            DispatchQueue.main.async {
                self.parent.text = textView.text
                self.parent.attributedText = textView.attributedText
            }
        }
    }
}

Der Schlüssel ist die Bindung von gewünschter Höhe, die in updateUIView mit der UIView sizeThatFits-Methode berechnet wird. Beachten Sie, dass dies in einen DispatchQueue.main.async-Block eingeschlossen ist, um den SwiftUI-Fehler "Status während der Aktualisierung der Ansicht ändern" zu vermeiden.

Ich kann diese Ansicht jetzt in meiner ContentView verwenden:

struct ContentView: View {

    @State private var notes: [String?] = [
        "This is a short Note",
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet consectetur. Morbi enim nunc faucibus a. Nunc pulvinar sapien et ligula ullamcorper malesuada proin libero.",
    ]

    @State private var desiredHeight: [CGFloat] = [0, 0]

    var body: some View {
        List {
            ForEach(0..<notes.count, id: \.self) { index in
                TextView(
                    desiredHeight: self.$desiredHeight[index],
                    text: self.$notes[index],
                    attributedText: .constant(nil)
                )
                .frame(height: max(self.desiredHeight[index], 100))
            }
        }
    }
}

Hier habe ich ein paar Notizen in einem String-Array zusammen mit einem Array von gewünschten Höhe-Werten, die an die Textansicht gebunden werden sollen. Die Höhe der Textansicht wird im Rahmenmodifikator der Textansicht festgelegt. In diesem Beispiel habe ich auch eine Mindesthöhe festgelegt, um Platz für die anfängliche Bearbeitung zu schaffen. Die Rahmenhöhe wird nur aktualisiert, wenn sich einer der Statuswerte (in diesem Fall die Notizen) ändert. Bei der Implementierung der Textansicht hier tritt dies nur auf, wenn die Bearbeitung in der Textansicht endet.

Ich habe versucht, den Text in der Delegatenfunktion textViewDidChange im Koordinator zu aktualisieren. Dadurch wird die Rahmenhöhe beim Hinzufügen von Text aktualisiert, sodass Sie jedoch immer nur Text am Ende der Textansicht eingeben können, da durch das Aktualisieren der Ansicht die Einfügemarke auf das Ende zurückgesetzt wird!

RocketDoc
quelle