【SwiftUI × CoreData 검색 필터링

전제 조건


며칠 전 SwiftUI× CoreData를 사용하여 CRUD 기능을 갖춘 메모장 애플리케이션을 제작했습니다.
https://zenn.dev/tomsan96/articles/e76a1088bcf78d
나는 이 프로그램에 검색 기능을 추가할 것이다.

컨디션


Swift5
Xcode13.1
iOS15
(iOS15 이상searchable 모뎀을 사용합니다.)

검색 필터링 기능 구현


메모는 응용 프로그램의 소스 코드에서 표시 부분을 요약합니다.
import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        entity: Memo.entity(),
        sortDescriptors: [NSSortDescriptor(key: "updatedAt", ascending: false)],
        animation: .default
    ) var fetchedMemoList: FetchedResults<Memo>

    var body: some View {
        NavigationView {
            List {
                ForEach(fetchedMemoList) { memo in
                    NavigationLink(destination: EditMemoView(memo: memo)) {
                        VStack {
                            Text(memo.title ?? "")
                               .font(.title)
                               .frame(maxWidth: .infinity,alignment: .leading)
                               .lineLimit(1)
                           HStack {
                               Text (memo.stringUpdatedAt)
                                   .font(.caption)
                                   .lineLimit(1)
                               Text(memo.content ?? "")
                                   .font(.caption)
                                   .lineLimit(1)
                               Spacer()
                           }
                        }
                    }
                }
                .onDelete(perform: deleteMemo)
            }
            .navigationTitle("メモ")
            .navigationBarTitleDisplayMode(.automatic)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    NavigationLink(destination: AddMemoView()) {
                        Text("新規作成")
                       
                    }
                }
            }
        }
    }
    
    private func deleteMemo(offsets: IndexSet) {
        offsets.forEach { index in
            viewContext.delete(fetchedMemoList[index])
        }
        try? viewContext.save()
    }
}

그러면 이 일람표시에 검색 기능을 추가합니다.
  • 검색어를 감시하는 속성 추가
  • @State private var searchText: String = ""
    
  • searchable 모뎀 구현
  • NavigationView {
    // 省略
    }
    .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "検索")
    
    placement에서 표시 위치를 결정할 수 있으며, 이번에는 자주 표시.navigationBarDrawer(displayMode: .always)를 위해 사용한다.
    또한 prompt를 통해 평면 유지기를 설정할 수 있다.
  • onChange에 입력할 때마다 검색 조건의 판정
  • // searchableの下に追加
    .onChange(of: searchText) { newValue in
        search(text: newValue)
    }
    

  • 실시onChange에 명시된 search 방법
    CoreData는 NSPredicate를 사용하여 검색 조건을 지정할 수 있습니다.
  •     private func search(text: String) {
    	if text.isEmpty {
    	    fetchedMemoList.nsPredicate = nil // ①
    	} else {
    	    let titlePredicate: NSPredicate = NSPredicate(format: "title contains %@", text) // ②
    	    let contentPredicate: NSPredicate = NSPredicate(format: "content contains %@", text) // ③
    	    fetchedMemoList.nsPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [titlePredicate, contentPredicate]) // ④
    	}
        }
    

    ① 검색이 비어 있을 경우 검색 조건을 필터링할 필요가 없음
    %@ 문자열로 처리한다는 뜻
    "title에 문자열 text 포함"을 검색 조건으로 합니다
    ③ "conent에 문자열 text 포함"을 검색 조건으로 함
    NSCompoundPredicate로 여러 조건 연결orPredicateWithSubpredicates OR 조건으로 배열을 연결하는 내용
    이렇게 하면 제목이나 노트 내용이 검색 텍스트 부분과 일치하는 노트만 추출할 수 있다.

    전체 이미지
    import SwiftUI
    import CoreData
    
    struct ContentView: View {
        @Environment(\.managedObjectContext) private var viewContext
        @FetchRequest(
            entity: Memo.entity(),
            sortDescriptors: [NSSortDescriptor(key: "updatedAt", ascending: false)],
            animation: .default
        ) var fetchedMemoList: FetchedResults<Memo>
        @State private var searchText: String = ""
            
        var body: some View {
            NavigationView {
                List {
                    ForEach(fetchedMemoList) { memo in
                        NavigationLink(destination: EditMemoView(memo: memo)) {
                            VStack {
                                Text(memo.title ?? "")
                               .font(.title)
                               .frame(maxWidth: .infinity,alignment: .leading)
                               .lineLimit(1)
                            HStack {
                                Text (memo.stringUpdatedAt)
                                   .font(.caption)
                                   .lineLimit(1)
                                Text(memo.content ?? "")
                                   .font(.caption)
                                   .lineLimit(1)
                                Spacer()
                               }
                            }
                        }
                    }
                    .onDelete(perform: deleteMemo)
                }
                .navigationTitle("メモ")
                .navigationBarTitleDisplayMode(.automatic)
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        NavigationLink(destination: AddMemoView()) {
                            Text("新規作成")
                        }
                    }
                }
            }
            .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "検索")
            .onChange(of: searchText) { newValue in
                search(text: newValue)
            }
        }
        
        private func deleteMemo(offsets: IndexSet) {
            offsets.forEach { index in
                viewContext.delete(fetchedMemoList[index])
            }
            try? viewContext.save()
        }
        
        private func search(text: String) {
            if text.isEmpty {
                fetchedMemoList.nsPredicate = nil
            } else {
                let titlePredicate: NSPredicate = NSPredicate(format: "title contains %@", text)
                let contentPredicate: NSPredicate = NSPredicate(format: "content contains %@", text)
                fetchedMemoList.nsPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [titlePredicate, contentPredicate])
            }
        }
    }
    

    참고 자료


    https://qiita.com/yusuga/items/8fd531ebd8f5e72bb97b
    https://hachinobu.hateblo.jp/entry/20130123/1358904735
    https://useyourloaf.com/blog/making-swiftui-views-searchable/

    좋은 웹페이지 즐겨찾기