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でフレームサイズを変更しようとしましたが、効果がありませんでした。この段階では、ビューはレイアウトでもなく、そのフレームはどこか他の場所でオーバーライドされていると思います。誰かが私を正しい方向に向けてくれたら本当にありがたいです。
カプセル化ビューがフレームの高さを設定するために使用する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の最後にのみテキストを入力できるようになります。