web-dev-qa-db-ja.com

UITextView-SwiftUIのコンテンツに基づいてサイズを調整します

UITextViewのサイズをSwiftUIのコンテンツに依存させる方法を理解しようとしています。次のように、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でフレームサイズを変更しようとしましたが、効果がありませんでした。この段階では、ビューはレイアウトでもなく、そのフレームはどこか他の場所でオーバーライドされていると思います。誰かが私を正しい方向に向けてくれたら本当にありがたいです。

8
user3559787

カプセル化ビューがフレームの高さを設定するために使用する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
            }
        }
    }
}

重要なのは、UIViewのsizeThatFitsメソッドを使用してupdateUIViewで計算されるdesiredHeightのバインディングです。これは、SwiftUIの「ビュー更新中の状態の変更」エラーを回避するためにDispatchQueue.main.asyncブロックでラップされていることに注意してください。

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値の配列とともに、String配列にいくつかのメモがあります。 TextViewの高さは、TextViewのフレーム修飾子で設定されます。この例では、最初の編集用のスペースを確保するために、最小の高さも設定しています。フレームの高さは、いずれかの状態値(この場合はノート)が変更されたときにのみ更新されます。ここでのTextViewの実装では、これは編集がテキストビューで終了したときにのみ発生します。

CoordinatorのtextViewDidChangeデリゲート関数のテキストを更新してみました。これにより、テキストを追加するときにフレームの高さが更新されますが、ビューを更新すると挿入ポイントが最後にリセットされるため、TextViewの最後にのみテキストを入力できるようになります。

1
RocketDoc