複数行のテキストを入力表示する
Published by @SoNiceInfo at 6/24/2020
iOS 14対応
WWDC20で発表された新しいSwiftUIからTextEditorが追加されました。
iOS13ではUITextViewをSwiftUIでラップしていましたがTextEditorの登場により不要となりました。
複数のテキストを表示する際にはTextの修飾子.lineLimit(nil)を使って複数行にできます。
import SwiftUI
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
// 入力
TextEditor(text: $text)
.frame(width: UIScreen.main.bounds.width * 0.8, height: 200)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.blue, lineWidth: 5)
)
// 表示
Text(text)
.foregroundColor(.yellow)
.lineLimit(nil)
.padding(5)
.frame(width: UIScreen.main.bounds.width * 0.8, height: 200, alignment: .topLeading)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.green, lineWidth: 5)
)
}
}
}iOS 13対応
複数のテキストを表示する際にはTextの修飾子.lineLimit(nil)を使って複数行にできます。
しかし、入力の際にTextField().lineLimit(nil)としても複数行にはなりません。
そこで、UIViewRepresentableを使ってUIKitのUITextViewをSwiftUIで使えるようにラップします。
複数行に対応したテキストフィールドの構造体を用意する
struct MultilineTextField: UIViewRepresentable {
@Binding var text: String
}複数行のテキスト入力に対応したMultilineTextFieldを作ります。
その際、UIViewRepresentableプロトコルに準拠させます。
こうすることでUITextViewを使うことができるようになります。
また、MultilineTextFieldはBindingされた変数textを受け取って参照・変更できるようにします。
UITextViewでテキストフィールドを作る
struct MultilineTextField: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.isScrollEnabled = true
view.isEditable = true
view.font = UIFont.systemFont(ofSize: 18)
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
if uiView.text != text {
uiView.text = text
}
}
}次に、Viewが作られたときに呼ばれるmakeUIViewと更新があったときに呼ばれるupdateUIViewを実装していきます。makeUIViewでUITextViewのインスタンスを作成しています。view.isScrollEnabled = trueでスクロールの可否、view.isEditable = trueで編集可能なテキストフィールドにしています。updateUIViewで、変数textが変更されたら、MultilineTextFieldの内容も変更するようにしています。
テキストの変更を親Viewを通知する
struct MultilineTextField: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
view.delegate = context.coordinator
return view
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: MultilineTextField
init(_ textView: MultilineTextField) {
self.parent = textView
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
}
}このままではMultilineTextFieldで変更した変数textが親Viewに通知されず使いづらいです。Coordinatorインスタンスを作成して子Viewから親Viewへ変更通知をします。makeUIViewでもview.delegate = context.coordinatorを追加します。
完成
今までの説明をまとめるとこのようになります。
SwiftUIの通常のTextField()のようにMultilineTextField(text: $text)で呼び出します。
Viewの修飾子が使えます。今回は青枠が作成したMultilineTextField(text: $text)です。
緑枠に結果が表示されます。
//
// ContentView.swift
//
import SwiftUI
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
// 入力
MultilineTextField(text: $text)
.frame(width: UIScreen.main.bounds.width * 0.8, height: 200)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.blue, lineWidth: 5)
)
// 表示
Text(text)
.foregroundColor(.yellow)
.lineLimit(nil)
.padding(5)
.frame(width: UIScreen.main.bounds.width * 0.8, height: 200, alignment: .topLeading)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.green, lineWidth: 5)
)
}
}
}
// 複数行入力するためのTextField
struct MultilineTextField: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.delegate = context.coordinator
view.isScrollEnabled = true
view.isEditable = true
view.isUserInteractionEnabled = true
view.font = UIFont.systemFont(ofSize: 18)
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
if uiView.text != text {
uiView.text = text
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: MultilineTextField
init(_ textView: MultilineTextField) {
self.parent = textView
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
キーボードが邪魔で表示が隠れてしまってますね。
こんなときは「キーボードを閉じる」を実装しましょう。
参考
ios - How do I create a multiline TextField in SwiftUI? - Stack OverflowSwiftUI Multiline Text - Swiftly Dierkes

