1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【SwiftUI, CoreData】データ登録と動的絞り込みの両方で再描画させる

Last updated at Posted at 2022-08-04

はじめに

自分が初心者なので初心者向けです。無理くり解決したもののベストプラクティスではないだろうと思っています。良い方法があればコメントなどお願いいたします。

やりたかったこと、困ったこと

データの登録(CoreData更新)と絞り込み(predicate変更)のそれぞれで再描画させるのは簡単だったのですが、両方で再描画させるには工夫が必要でした。

両方に対応できた結果のGIFはこちら↓
gif_memo_label.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に連動させる形を意識しました。大ざっぱにこんな考え方です。

  • 動的な絞り込みのやり方を変える
  1. 子ビューshowDataView@FetchRequestで絞り込み条件を指定
  2. 条件は変数にしたいが、引数や同列で宣言したStateは使えない。なので グローバル変数を使う
  3. グローバル変数を@Stateと連動させる

上記1,2部分のコード抜粋がこちら。グローバル変数labelFilterGlobal@FetchRequestpredicateに使っています

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 ?? "")
                }
            }
        }
    }
}

image.png
おわり。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?