複数行のテキストを入力表示する
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