首页 > 解决方案 > 使用 `UIViewRepresentable` 来桥接 `UITextView`,它会增长和缩小以使其文本适合 SwiftUI

问题描述

如果我想要一个占据整个宽度并垂直增长和收缩以适应其文本的非滚动UITextView,我可以在 UIKit 中这样做:

import UIKit

class TestViewController: UIViewController {
    private lazy var textView = UITextView()

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let view = view else { return }

        view.backgroundColor = .white

        textView.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
        textView.backgroundColor = .systemYellow
        textView.isEditable = true
        textView.isSelectable = true
        textView.isScrollEnabled = false // We want the view to resize to fit text instead of scrolling

        view.addSubview(textView)

        textView.translatesAutoresizingMaskIntoConstraints = false
        let constraints = [
            textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
        ]

        NSLayoutConstraint.activate(constraints)
    }
}

它看起来像这样:

布局合理

我想通过UITextViewUIViewRepresentable. 我是这样做的,文本视图的配置与 UIKit 示例中的完全相同:

import SwiftUI
import UIKit

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator

        textView.backgroundColor = .clear
        textView.isEditable = true
        textView.isSelectable = true
        textView.isScrollEnabled = false // We want the view to resize to fit text instead of scrolling

        // Makes the text wrap rather than extend on one line outside the parent frame
        textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

        return textView
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(text: _text)
    }

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

extension TextView {
    class Coordinator: NSObject, UITextViewDelegate {
        @Binding var text: String

        init(text: Binding<String>) {
            self._text = text
        }

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

并像这样在 SwiftUI 中使用它:

import SwiftUI

struct ContentView: View {
    @State var text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

    var body: some View {
        VStack(alignment: .leading) {
            TextView(text: $text)
                .background(Color.yellow)
            Spacer()
        }
    }
}

设置textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)成功会使文本视图将其文本包装成多行,但高度会填满整个屏幕:

包裹但高度错误

添加textView.setContentHuggingPriority(.defaultHigh, for: .vertical)确实会缩小高度,但现在换行不再起作用;所有文本都在一行中,延伸到框架之外:

不再包装

我在文档或在线中没有找到太多关于如何UIViewRepresentable将 UIKit 的布局连接到 SwiftUI 的信息。有没有办法实现这种自动增长和缩小以适应行为?或者我是否必须sizeThatFits在文本更改时手动设置框架?

标签: swiftuiuitextviewuiviewrepresentable

解决方案


推荐阅读