SwiftUI中的UITextView - 根据内容调整大小

11

我正试图弄清楚如何在SwiftUI中使UITextView的大小根据其内容而定。我将UITextView包装在UIViewRepresentable中,代码如下:

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

此外,我尝试根据其内容大小在updateUIView中更改框架大小,但没有任何效果。我想,在这个阶段,视图甚至没有被布局,它的框架被其他地方覆盖了。如果有人能指点我正确的方向,我将不胜感激。

2个回答

12

我通过在TextView中绑定一个变量,让包含它的视图使用该变量来设置框架高度,最终使其正常工作。以下是最简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
            }
        }
    }
}

关键是绑定desiredHeight,该值通过使用UIView sizeThatFits方法在updateUIView中计算。请注意,这被包装在DispatchQueue.main.async块中,以避免SwiftUI“在视图更新期间修改状态”错误。

现在我可以在我的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))
            }
        }
    }
}

这里有一个包含字符串数组和希望绑定到TextView的desiredHeight值数组的笔记。 TextView的高度通过TextView上的frame修饰符设置。 在此示例中,我还设置了最小高度以给予一些空间进行初始编辑。 当状态值之一(在此示例中为注释)更改时,框架高度仅更新。 在此处实现TextView时,只有在文本视图上的编辑结束时才会发生这种情况。

我尝试在Coordinator的textViewDidChange委托函数中更新文本。 这会在添加文本时更新框架高度,但会使您只能在TextView的末尾键入文本,因为更新视图会将插入点重置为末尾!


2
如果您的UITextView是可滚动的,则此方法可以正常工作。但是,如果您想要一个带有属性文本的静态多行文本视图,则此方法不起作用。 - Xaxxus
1
@Xaxxus 那个问题的解决方法是将 bounces = false。当滚动视图的高度等于其内容高度时,滚动将被禁用,并且 bounces = false。 - I. Pedan

0
在我的情况下,问题是视图的高度不能被屏幕的比例因子整除。因此,您必须向上舍入到下一个更高的比例。 例如:TextView 的高度为 172 像素,但这个高度不能被 iPhone X 的比例因子 3 整除,因此必须增加到 174。
 func updateUIView(_ uiView: UITextView, context: Context) {
    uiView.attributedText = attributedText

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

    DispatchQueue.main.async {
        self.height = roundToClosestHeight(newSize.height)
    }
}

func roundToClosestHeight(_ height: CGFloat) -> CGFloat {
    let scale = UIScreen.main.scale
    let difference = height.truncatingRemainder(dividingBy: scale)

    guard difference != 0 else { return height }
    return height + (scale - difference)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接