UITextView - ajuste o tamanho com base no conteúdo do SwiftUI

8

Estou tentando descobrir como tornar o tamanho do UITextView dependente do conteúdo do SwiftUI. Embrulhei o UITextView da UIViewRepresentableseguinte maneira:

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
        }
    }
}

Além disso, tentei alterar o tamanho do quadro no updateUIView com base no tamanho do conteúdo, mas não teve nenhum efeito. Suponho que, nesse estágio, a exibição não seja nem layout e seu quadro esteja sendo substituído em outro lugar. Eu realmente apreciaria se alguém pudesse me indicar uma direção correta.

Adão
fonte

Respostas:

2

Consegui fazer isso funcionar vinculando uma variável no TextView que é usada pela exibição de encapsulamento para definir a altura do quadro. Aqui está a implementação mínima do TextView:

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
            }
        }
    }
}

A chave é a ligação de desejadoHeight, que é calculada em updateUIView usando o método UIView sizeThatFits. Observe que isso está agrupado em um bloco DispatchQueue.main.async para evitar o erro "SwiftUI state Modifying during view update" do SwiftUI.

Agora posso usar essa exibição no meu ContentView:

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))
            }
        }
    }
}

Aqui eu tenho algumas notas em uma matriz String, juntamente com uma matriz de valores desejadosHeight para vincular ao TextView. A altura do TextView é definida no modificador de quadro no TextView. Neste exemplo, também defino uma altura mínima para fornecer espaço para a edição inicial. A altura do quadro é atualizada somente quando um dos valores do estado (neste caso, as notas) é alterado. Na implementação do TextView aqui, isso ocorre apenas quando a edição termina na exibição de texto.

Tentei atualizar o texto na função delegada textViewDidChange no Coordenador. Isso atualiza a altura do quadro à medida que você adiciona texto, mas permite que você possa digitar apenas o texto no final do TextView, pois a atualização do modo de exibição redefine o ponto de inserção até o final!

RocketDoc
fonte