
【SwiftUI】プレースホルダー付きのTextEditorを自作してみた
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
SwiftUIのTextEditorにプレースホルダーを付けたかったのですが、標準のAPIではプレースホルダーがありませんでした。良い解決方法がなかなか見当たらず、試行錯誤しながら自作してみることにしました。
作ったもの

環境
- Xcode 13.3
- iOS 15.4
TextEditorWithPlaceholder
こちらがプレースホルダー付きのTextEditorになります。
import SwiftUI
struct TextEditorWithPlaceholder: View {
@FocusState private var focusedField: Field?
enum Field {
case textEditor
case placeholder
}
@Binding var text: String
private let placeholderText: String
init(_ placeholder: String, text: Binding<String>) {
self._text = text
self.placeholderText = placeholder
}
var body: some View {
ZStack {
// テキストが空の時にプレースホルダーを表示する
if text.isEmpty {
ZStack {
Rectangle()
.fill(Color(uiColor: .systemBackground))
.onTapGesture {
focusedField = .placeholder
}
VStack {
HStack {
TextField(placeholderText, text: $text)
.focused($focusedField, equals: .placeholder)
.onAppear {
focusedField = .placeholder
}
Spacer()
}
.padding(.leading, 6)
.padding(.top, 8)
Spacer()
}
}
// テキストが空ではない時にTextEditorを表示する
} else {
TextEditor(text: $text)
.focused($focusedField, equals: .textEditor)
.onAppear {
focusedField = .textEditor
}
}
}
}
}
FocusState
今回はFocusStateを用いて、プレースホルダー用のTextFieldとTextEditorのフォーカス状態を管理します。
@FocusState private var focusedField: Field?
enum Field {
case textEditor
case placeholder
}
init
TextFieldWithPlaceholder生成時には、プレースホルダー用のテキストと、バインディングするテキストを設定します。
@Binding var text: String
private let placeholderText: String
init(_ placeholder: String, text: Binding<String>) {
self._text = text
self.placeholderText = placeholder
}
body
今回はテキストが空の時にはTextFieldを表示して、テキストが空ではない時はTextEditorを表示するようにしました。テキストが空の時にTextFieldを表示するのは、TextFieldのプレースホルダーをそのまま使用する為です。
テキストが空の時にはTextFieldを表示
if text.isEmpty {
ZStack {
Rectangle()
// TextEditorっぽく見せる為に背景を設置
.fill(Color(uiColor: .systemBackground))
// 背景がタップされた時にもプレースホルダー用TextFieldにフォーカスを当てる
.onTapGesture {
focusedField = .placeholder
}
VStack {
HStack {
TextField(placeholderText, text: $text)
// FocusStateで.placeholderでフォーカスが当たるようにする
.focused($focusedField, equals: .placeholder)
// テキストが空になり、TextFieldが表示された時にフォーカスが当たるようにする
.onAppear {
focusedField = .placeholder
}
Spacer()
}
.padding(.leading, 6)
.padding(.top, 8)
Spacer()
}
}
まずTextFieldをTextEditorっぽく見せる為に背景を設置しています。
TextFieldの.focused($focusedField, equals: .placeholder)で.placeholderがfocusedFieldに代入された時にこのTextFieldにフォーカスが当たるようにしています。そして、こちらの背景がタップされた時にTextFieldにフォーカスが当たるように実装しました。
また、テキストが空ではない状態から空になった時にも、TextFieldにフォーカスが当たる状態にしたかった為、onAppear時にもフォーカスされるようにしました。
.paddingでプレースホルダーの見え方がTextEditorと同じように見えるように調整しています。
テキストが空では無い時にTextEditorを表示
} else {
TextEditor(text: $text)
// FocusStateで.textEditorでフォーカスが当たるようにする
.focused($focusedField, equals: .textEditor)
// テキストが空で無くなり、TextEditorが表示された時にフォーカスが当たるようにする
.onAppear {
focusedField = .textEditor
}
}
.focused($focusedField, equals: .textEditor)で.textEditorがfocusedFieldに代入された時にこのTextEditorにフォーカスが当たるようにしています。
また、テキストが空では無くなるとこのTextEditorが表示されるのですが、その時にTextEditorにフォーカスが当たる状態にしたかった為、onAppear時にもフォーカスが当たるようにしています。
使い方
自作したプレースホルダー付きTextEditorは、第一引数にプレースホルダーを渡して、第二引数にバインディングするテキストを渡して使います。
TextEditorWithPlaceholder("Write down...", text: $text)
ContentView
今回のサンプルデモで実装したContentViewは下記の内容になります。
struct ContentView: View {
@FocusState private var isFocusedTextEditor: Bool
@State private var text: String = ""
var body: some View {
ZStack {
Rectangle()
.fill(.yellow)
.ignoresSafeArea()
VStack {
Text("Memo pad")
.font(.title)
TextEditorWithPlaceholder("Write down...", text: $text)
.focused($isFocusedTextEditor)
}
.padding()
}
.onTapGesture {
isFocusedTextEditor = false
}
}
}
今回のコード
GitHubに上げております。
おわりに
プレースホルダー付きのTextEditorを自作することは出来ましたが、かなり強引な方法で作成しました。そもそも標準で提供していないので、TextEditorにはプレースホルダーは必要のないという見解なのか、それとも今後いつか追加されるのか興味津々です。こんなに強引に実装しなくてもより良い方法があるよ等ありましたら教えていただけると幸いです。
さぁ、今年のWWDCもいよいよですね。どんな楽しいニュースが待っているのでしょうか?

モバイルアプリ開発のチームメンバー絶賛募集中!
モバイル事業部では事業会社様と一緒に、数年間にわたり長期でモバイルアプリをグロースさせています。
そんなモバイルアプリ開発のチームメンバーを絶賛募集中です!
もちろんモバイルアプリ開発以外のエンジニアも募集中です!










