はじめに
自分が初心者なので初心者向けです。無理くり解決したもののベストプラクティスではないだろうと思っています。良い方法があればコメントなどお願いいたします。
やりたかったこと、困ったこと
データの登録(CoreData更新)と絞り込み(predicate変更)のそれぞれで再描画させるのは簡単だったのですが、両方で再描画させるには工夫が必要でした。
両方に対応できた結果のGIFはこちら↓
「登録」を押したタイミングで表示が増え、「ラベルで絞り込み」も機能しています
(ContentViewの全コードは最下部に掲載)
これの何が難しかったかですが、下記の動的絞り込みを実装すると、データ追加のタイミングで再描画されなくなってしまいます
- 動的な絞り込みの例
memos.nsPredicate = NSPredicate(format: "label contains %@", "a")
- データ登録の例
let newMemo = Memo(context: viewContext)
newMemo.text = "Memo1"
newMemo.label = "aaa"
newMemo.created_at = Date()
do {
try viewContext.save()
} //・・・以下略・・・
解決策と考え方
色んなやり方があると思いますが、動的絞り込み用の@State
に連動させる形を意識しました。大ざっぱにこんな考え方です。
- 動的な絞り込みのやり方を変える
- 子ビュー
showDataView
の@FetchRequest
で絞り込み条件を指定 - 条件は変数にしたいが、引数や同列で宣言したStateは使えない。なので グローバル変数を使う
- グローバル変数を
@State
と連動させる
上記1,2部分のコード抜粋がこちら。グローバル変数labelFilterGlobal
を@FetchRequest
のpredicate
に使っています
var labelFilterGlobal:String = ""
struct showDataView: View{
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Memo.created_at, ascending: true)],
predicate: labelFilterGlobal == "" ? nil : NSPredicate(format: "label contains %@", labelFilterGlobal),
animation: .default
) private var memos: FetchedResults<Memo>
上記3については、さらに細かくいうと下記のような考え方です
3-1. 親ビューContentView
で絞り込み条件の@State
であるlabelFilterState
を宣言。
3-2. @State
が変更された時の再描画時にグローバル変数labelFilterGlobal
に代入して連動させる
これで、「親ビューの@state変更
->グローバル変数更新->子ビューの@FetchRequest
で絞り込み」の順に動作させられます
struct ContentView: View {
@State var labelFilterState:String = ""
var body: some View {
get{
labelFilterGlobal = labelFilterState
return(
showDataView()
)
//・・・以下略・・・
ContentView全体のコード
最後に冒頭のGIF動画を作った時に動かした全体のコードを掲載します。
(Preview部分はコメントアウトしてシミュレーターをビルドしました)
import SwiftUI
var labelFilterGlobal:String = ""
struct ContentView: View {
@State var labelFilterState:String = ""
var body: some View {
get{
labelFilterGlobal = labelFilterState
return(
VStack(spacing: 15){
Spacer().frame(height:30)
addDataView()
Divider()
showDataView()
TextField("ラベルで絞り込み", text: $labelFilterState)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 150)
Spacer()
}
)
}
}
}
struct addDataView: View{
@State var textInputState:String = ""
@State var labelInputState:String = ""
@Environment(\.managedObjectContext) private var viewContext
var body: some View {
VStack{
Text("メモとラベルを登録").font(.headline)
TextField("メモを入力", text: $textInputState)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 150)
TextField("ラベルを入力", text: $labelInputState)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 150)
Button("登録"){ addMemo() }
}
}
func addMemo() {
withAnimation {
let newMemo = Memo(context: viewContext)
newMemo.text = textInputState
newMemo.label = labelInputState
newMemo.created_at = Date()
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
struct showDataView: View{
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Memo.created_at, ascending: true)],
predicate: labelFilterGlobal == "" ? nil : NSPredicate(format: "label contains %@", labelFilterGlobal),
animation: .default
) private var memos: FetchedResults<Memo>
var body: some View {
VStack{
Text("登録済みのメモとラベル").font(.headline)
ForEach(memos) { memo in
HStack(spacing:0){
Text(memo.text ?? "")
Spacer().frame(width:20)
Text("#")
Text(memo.label ?? "")
}
}
}
}
}