如何在 SwiftUI 中获取 UITextView 的动态高度

atd*_*nsm 6 swiftui uiviewrepresentable

我非常接近在 SwiftUI 中实现 UITextView 的动态高度。请帮我解决这些问题:

  1. UITextView 在出现时具有正确的高度,但在执行编辑时不会调整高度;我希望它能调整一下。
  2. 每次我在 TextView 中编辑文本时,我都会在控制台中收到此消息:[SwiftUI] Modifying state during view update, this will cause undefined behavior.

这是我的代码:

项目编辑视图

TextView(text: $notes, textViewHeight: $textViewHeight)
    .frame(height: self.textViewHeight)
Run Code Online (Sandbox Code Playgroud)

UI文本视图

import SwiftUI

struct TextView: UIViewRepresentable {
    @Binding var text: String
    @Binding var textViewHeight: CGFloat

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = .systemFont(ofSize: 17)
        textView.backgroundColor = .clear

        return textView
    }

    func updateUIView(_ textView: UITextView, context: Context) {
        textView.text = text
    }

    class Coordinator: NSObject, UITextViewDelegate {
        var control: TextView

        init(_ control: TextView) {
            self.control = control
        }

        func textViewDidChange(_ textView: UITextView) {
            control.text = textView.text
        }
    }

    func makeCoordinator() -> TextView.Coordinator {
        Coordinator(self)
    }
}
Run Code Online (Sandbox Code Playgroud)

有人问过类似的问题,但没有一个解决方案对我有用。

小智 6

For anyone who comes across this thread in the future. I researched through the suggested answers and to me they weren't ideal. As of iOS 16, UIViewRepresentable has a overridable function sizeThatFits, this function will allow you to calculate the size of the resulting SwiftUI view using the proposed parent dimensions. Example below:

func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? {
    let dimensions = proposal.replacingUnspecifiedDimensions(
        by: .init(
            width: 0,
            height: CGFloat.greatestFiniteMagnitude
        )
    )
            
    let calculatedHeight = calculateTextViewHeight(
        containerSize: dimensions,
        attributedString: uiView.attributedText
    )
    
    return .init(
        width: dimensions.width,
        height: calculatedHeight
    )
}

private func calculateTextViewHeight(containerSize: CGSize,
                                     attributedString: NSAttributedString) -> CGFloat {
    let boundingRect = attributedString.boundingRect(
        with: .init(width: containerSize.width, height: .greatestFiniteMagnitude),
        options: [.usesLineFragmentOrigin, .usesFontLeading],
        context: nil
    )
    
    return boundingRect.height
}
Run Code Online (Sandbox Code Playgroud)

Note: For this calculation method to work correctly, you may need to update the UITextView properties in the makeUIView function:

    textView.textContainer.lineFragmentPadding = 0
    textView.textContainerInset = .zero
Run Code Online (Sandbox Code Playgroud)