SwiftUIで「全高さを使うTextEditor」を実現する
SwiftUIで「全高さを使うTextEditor」を実現する
SwiftUIで、文字入力を受け付ける TextEditor
を「最大高さ」で伸ばし、スクロール可能にする方法を整理します!
まず、FlutterやDartとは違って、SwiftUIのTextEditor
は「自然に高さを調整する」ようにはできません。
そこで、自分で調整する必要があります(UIKitのUITextViewを使うとより簡単になりますが)。
SwiftUIのTextEditorの特性と限界
- SwiftUI標準の
TextEditor
はmulti-line入力に特化したコンポーネントです。 - しかし、文字量によって自動で高さを変更する機能はないため、基本的に高さを固定するか、スクロールするしかありません。
- 高さを柔軟に変える場合、内部的にUIKitの
UITextView
を使う必要が出てきます。
TextEditor
自体は、SwiftUIのViewサイクルにうまく乗っていますが、コンテンツサイズの検知・動的なframe更新といった機能までは標準で備えていないのです。
どうやって動的な高さを実現するか(原理解説)
SwiftUIにはUIKitのようなsizeToFit
やcontentSize
という考え方がありません。そこで:
- UIKitの
UITextView
をUIViewRepresentable
でラップしてSwiftUIに持ち込む -
UITextView
のcontentSize
またはsizeThatFits
を使い、必要な高さを計算する - その計算結果をSwiftUI側のStateに反映させて、Viewの高さを更新する
これにより、入力内容に応じて伸び縮みするエディタが作れます。
もし "全高を使いたい" 場合は、高さの計算すらせず、常に.frame(maxHeight: .infinity)
を指定して、TextEditorが親の最大サイズに広がるようにします。
ここではこの"全高使い切り型"を紹介します。
実装サンプル
FullSizeTextEditor.swift
import SwiftUI
struct FullSizeTextEditor: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.isScrollEnabled = true // コンテンツが増えたらスクロール
textView.font = UIFont.preferredFont(forTextStyle: .body)
textView.backgroundColor = .clear
textView.delegate = context.coordinator
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
if uiView.text != text {
uiView.text = text
}
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
init(text: Binding<String>) {
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
text.wrappedValue = textView.text
}
}
}
使い方:TextEditorView.swift
struct TextEditorView: View {
@State private var text: String = ""
var body: some View {
VStack(spacing: 0) {
FullSizeTextEditor(text: $text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
.padding()
}
}
}
なぜこうする必要があるのか(もう少し深掘り)
- SwiftUIの
TextEditor
単体では"文字量に応じたサイズ調整"ができないため -
UITextView
のcontentSize
を利用すると、内容に応じた自然なレイアウトが可能になる - 全高を使う場合は、最大サイズを指定しつつ、スクロール許可することで、オーバーフローを防ぐ
- これにより、自然な入力体験と、レイアウトの安定性が両立できる
UIKitの知識をうまくSwiftUIに橋渡しするのがポイントです。
プラスアップ(さらに加えられると良いテクニック)
プレースホルダー表示
TextEditorにはデフォルトのプレースホルダーがないため、オーバーレイで実装します。
struct PlaceholderTextEditor: View {
@Binding var text: String
var placeholder: String
var body: some View {
ZStack(alignment: .topLeading) {
if text.isEmpty {
Text(placeholder)
.foregroundColor(.gray)
.padding(EdgeInsets(top: 8, leading: 4, bottom: 0, trailing: 0))
}
FullSizeTextEditor(text: $text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
マックス高さ制限(例:400ptを超えたらスクロール)
最大サイズを制限し、それ以上はスクロールさせる設計も可能です。
FullSizeTextEditor(text: $text)
.frame(maxWidth: .infinity)
.frame(minHeight: 100, maxHeight: 400) // 高さ制限
.background(Color(.systemGray6))
.cornerRadius(8)
キーボードに押し上げられないようにする
キーボードが出ても固定したい要素(例:送信ボタン)には、.ignoresSafeArea(.keyboard)
を指定します。
Button("送信") {
// 送信処理
}
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.ignoresSafeArea(.keyboard)
これらの工夫を組み合わせることで、よりプロダクト品質の高い入力体験が作れます。
おわりに
SwiftUIで「高さを入力に対応して変える」のは直接的にはサポートされていませんが、UIKitを組み合わせることで柔軟に実現できます。
実際のプロダクトでもこの手法はかなり役立つので、ぜひ覚えておきたいところです。
Discussion