1. 目的
SwiftUIでは、View(画面のstruct)が状態を保持しないので、プロパティラッパーを使用して状態を管理します。この記事では、複数行のテキスト(UIKitでのUITextView)を例に考察します。
2. コードの例
目的に関連するコードだけを抜粋しています。やりたいことは、プレースホルダー付きのUITextViewに相当するViewをSwiftUIで実装することです。
プレースホルダーは文字の入力があったら非表示とするため、#1のisEditedフラグを使用します。
この記事のポイントは、@Stateプロパティのtextです。
MultiLineTextField_Rep(子)の#2-2から、MultiLineTextField(親)の#2-1に入力値を渡しています。この受け渡しがない場合、#1のisEditedフラグが変更になってMultiLineTextField(親)が再描画される際、入力した値は引き継がれません(=Viewが状態を保持しないから)。
このときのUIの動作としては、キーボードの文字をタップすると、プレースホルダーが非表示になって、文字も入力されていない(1文字目が無視される)ように見えます。実際は、Viewが入力値(textの値)を保持せず消失しています。
import SwiftUI
struct MultiLineTextField: View {
@State var isEdited: Bool = false //#1-1
@State var text: String = "" //#2-1
var body: some View {
ZStack(){
if !isEdited {
Placeholder()
}
MultiLineTextField_Rep(text: $text, isEdited: $isEdited)
}
}
}
// Placeholder用のUILabel
struct Placeholder: UIViewRepresentable {
(省略)
}
struct MultiLineTextField_Rep: UIViewRepresentable {
@Binding var text: String
@Binding var isEdited: Bool
let textView = UITextView()
final class Coordinator: NSObject, UITextViewDelegate {
private var parent: MultiLineTextField_Rep
init(_ textView: MultiLineTextField_Rep) {
self.parent = textView
super.init()
self.setNotification()
}
(省略)
private func setNotification() {
// テキストが編集されたらPlaceholderを非表示とするための通知
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(textChanged(_:)),
name: UITextView.textDidChangeNotification,
object: nil)
}
@objc func textChanged(_ notification: NSNotification) {
parent.isEdited = parent.text.isEmpty ? false : true //#1-2
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text //#2-2
self.parent.textView.attributedText = NSAttributedString(string: textView.text,
attributes: self.parent.textAttribute)
}
}
}