はじめに
自分が調べた限りだと、SwiftUI単体では実装できそうにないので、UIKitを併用する方法で実装しました。
完成品
実装
まず、UIKitをSwiftUIから呼び出すためにUIViewRepresentableに準拠したViewを作成します。
説明はコード内のコメントを参照してください。
struct DynamicHeightTextview: UIViewRepresentable {
//入力値を反映するプロパティ
@Binding var text: String
//入力値を考慮したTextViewの高さを保持するプロパティ
@Binding var height: CGFloat
let textView = UITextView()
//実装必須
func makeUIView(context: Context) -> UITextView {
textView.backgroundColor = .clear
textView.font = .systemFont(ofSize: 16)
textView.delegate = context.coordinator
return textView
}
//実装必須
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
//Delegateメソッドを定義したクラスを返す
func makeCoordinator() -> Coordinator {
return Coordinator(dynamicHeightTextView: self)
}
//UITextViewのDelegateメソッドを実装する
class Coordinator: NSObject, UITextViewDelegate {
let dynamicHeightTextView: DynamicHeightTextview
let textView: UITextView
init(dynamicHeightTextView: DynamicHeightTextview) {
self.dynamicHeightTextView = dynamicHeightTextView
self.textView = dynamicHeightTextView.textView
}
func textViewDidChange(_ textView: UITextView) {
dynamicHeightTextView.text = textView.text
let textViewSize = textView.sizeThatFits(textView.bounds.size)
dynamicHeightTextView.height = textViewSize.height
}
}
}
続いて、DynamicHeightTextviewをもう1度ラップしたViewを作成し、ここで高さの反映とPlaceholderを追加します。
struct DynamicHeightTextEditor: View {
@Binding var text: String
@State var textHeight: CGFloat = 0
var placeholder: String
var minHeight: CGFloat
var maxHeight: CGFloat
//TextEditorの高さを保持するプロパティ
var textEditorHeight: CGFloat {
if textHeight < minHeight {
return minHeight
}
if textHeight > maxHeight {
return maxHeight
}
return textHeight
}
var body: some View {
ZStack {
//TextEditorの背景色
Color(red: 0.933, green: 0.917, blue: 0.956)
//Placeholder
if text.isEmpty {
HStack {
Text(placeholder)
.foregroundColor(Color(red: 0.62, green: 0.62, blue: 0.62))
.padding(.leading, 2)
Spacer()
}
}
DynamicHeightTextview(text: $text, height: $textHeight)
}
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.frame(height: textEditorHeight) //← ここで高さを反映
}
}
使用方法
struct ContentView: View {
@State var text = ""
var body: some View {
DynamicHeightTextEditor(text: $text, placeholder: "テキストを入力", minHeight: 35, maxHeight: 150)
.padding(.leading, 10)
.padding(.trailing, 10)
}
}
以上です。