100 Days of SwiftUI(DAY 58(Project12 part 2))

NSPredicateを使用した@FetchRequestのフィルタリング
Filtering @FetchRequest using NSPredicate

NSPredicate

name」と「universe」の2つの文字列属性を持つShipという新しいエンティティを作成します。

import CoreData
import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    //@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: nil) var ships: FetchedResults<Ship>
    
    //@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "universe == 'Star Wars'")) var ships: FetchedResults<Ship>
    
    //@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "universe == %@", "Star Wars")) var ships: FetchedResults<Ship>
    //@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "name < %@", "E")) var ships: FetchedResults<Ship>
    //@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "universe IN %@",["Aliens", "Firefly", "Star Trek"])) var ships: FetchedResults<Ship>
    //@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "name BEGINSWITH %@", "E")) var ships: FetchedResults<Ship>
    //@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "name BEGINSWITH[c] %@", "e")) var ships: FetchedResults<Ship>
    @FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "NOT name BEGINSWITH[c] %@", "e")) var ships: FetchedResults<Ship>

    var body: some View {
        VStack {
            List(ships, id: \.self) { ship in
                Text(ship.name ?? "Unknown name")
            }

            Button("Add Examples") {
                let ship1 = Ship(context: self.moc)
                ship1.name = "Enterprise"
                ship1.universe = "Star Trek"

                let ship2 = Ship(context: self.moc)
                ship2.name = "Defiant"
                ship2.universe = "Star Trek"

                let ship3 = Ship(context: self.moc)
                ship3.name = "Millennium Falcon"
                ship3.universe = "Star Wars"

                let ship4 = Ship(context: self.moc)
                ship4.name = "Executor"
                ship4.universe = "Star Wars"

                try? self.moc.save()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return ContentView().environment(\.managedObjectContext, context)
 
    }
}

BEGINSWITHand などの演算子を使用して、述語を使用して文字列の一部を調べることもできCONTAINSます。たとえば、これは大文字のEで始まるすべての船を返します。

CONTAINS[c] サブストリングで始まるのではなく、属性内のどこにでも置くことができることを除いて、同様に機能します。

より複雑な述語が必要な場合は、それらを使用ANDして必要な精度を構築するか、コアデータのインポートを追加して確認しNSCompoundPredicateます。これにより、いくつかの小さい述語から1つの述語を構築できます。

SwiftUIを使用した@FetchRequestの動的フィルタリング
Dynamically filtering @FetchRequest with SwiftUI

Singerという新しいコアデータエンティティを作成し、「firstName」と「lastName」という2つの文字列属性を割り当てます。データモデルインスペクタを使用して、CodegenをManual / Noneに変更し、[Editor]メニューに移動して[Create NSManagedObject Subclass]を選択すると、Singerカスタマイズ可能なクラスを取得できます。

Xcodeがファイルを生成したら、Singer + CoreDataProperties.swiftを開き、SwiftUIでクラスを使いやすくする次の2つのプロパティを追加します。

var wrappedFirstName: String {
    firstName ?? "Unknown"
}

var wrappedLastName: String {
    lastName ?? "Unknown"
}

import SwiftUI

struct FilteredList: View {
    var fetchRequest: FetchRequest<Singer>
    
    init(filter: String) {
        fetchRequest = FetchRequest<Singer>(entity: Singer.entity(), sortDescriptors: [], predicate: NSPredicate(format: "lastName BEGINSWITH %@", filter))
    }
    var body: some View {
        List(fetchRequest.wrappedValue, id: \.self) { singer in
            Text("\(singer.wrappedFirstName) \(singer.wrappedLastName)")
        }
        
    }
}
import CoreData
import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @State private var lastNameFilter = "A"

    var body: some View {
        VStack {
            // list of matching singers
            FilteredList(filter: lastNameFilter)

            Button("Add Examples") {
                let taylor = Singer(context: self.moc)
                taylor.firstName = "Taylor"
                taylor.lastName = "Swift"

                let ed = Singer(context: self.moc)
                ed.firstName = "Ed"
                ed.lastName = "Sheeran"

                let adele = Singer(context: self.moc)
                adele.firstName = "Adele"
                adele.lastName = "Adkins"

                try? self.moc.save()
            }

            Button("Show A") {
                self.lastNameFilter = "A"
            }

            Button("Show S") {
                self.lastNameFilter = "S"
            }
        }
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return ContentView().environment(\.managedObjectContext, context)
 
    }
}

さらに進みたいですか?

柔軟性を高めるために、FilteredListビューを改善して、あらゆる種類のエンティティで機能し、あらゆるフィールドでフィルタリングできるようにします。これを適切に機能させるには、いくつかの変更を加える必要があります。
Singerクラスを具体的に参照するのではなく、渡されるものはすべてでなければならないという制約のあるジェネリックスを使用しますNSManagedObject。
lastName属性を持たないエンティティを使用している可能性があるため、フィルタリングするキー名を決定するために2番目のパラメーターを受け入れる必要があります。
各エンティティに何が含まれるかは事前にわからないため、包含ビューに決定を任せます。したがって、歌手の名前のテキストビューを単に使用するのではなく、代わりに、ビューを構成するために実行できるクロージャーを要求します。

NSPredicate属性名を置き換えるために使用できる特別な記号があります%K。これにより、提供される値が挿入されますが、引用符は追加されません。正しい述語はこれです。

NSPredicate(format: "%K BEGINSWITH %@", filterKey, filterValue)

難解なので、後日参照用
SwiftUIを使用した@FetchRequestの動的フィルタリング

import SwiftUI
import CoreData

struct FilteredList<T: NSManagedObject, Content: View>: View {
    var fetchRequest: FetchRequest<T>
    var singers: FetchedResults<T> { fetchRequest.wrappedValue }

    // this is our content closure; we'll call this once for each item in the list
    let content: (T) -> Content

    var body: some View {
        List(fetchRequest.wrappedValue, id: \.self) { singer in
            self.content(singer)
        }
    }

    init(filterKey: String, filterValue: String, @ViewBuilder content: @escaping (T) -> Content) {
        fetchRequest = FetchRequest<T>(entity: T.entity(), sortDescriptors: [], predicate: NSPredicate(format: "%K BEGINSWITH %@", filterKey, filterValue))
        self.content = content
    }
}

import CoreData
import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @State private var lastNameFilter = "A"

    var body: some View {
        VStack {
            // list of matching singers
            //FilteredList(filter: lastNameFilter)
            
            // new version!!
            FilteredList(filterKey: "lastName", filterValue: lastNameFilter) { (singer: Singer) in
                Text("\(singer.wrappedFirstName) \(singer.wrappedLastName)")
            }

            Button("Add Examples") {
                let taylor = Singer(context: self.moc)
                taylor.firstName = "Taylor"
                taylor.lastName = "Swift"

                let ed = Singer(context: self.moc)
                ed.firstName = "Ed"
                ed.lastName = "Sheeran"

                let adele = Singer(context: self.moc)
                adele.firstName = "Adele"
                adele.lastName = "Adkins"

                try? self.moc.save()
            }

            Button("Show A") {
                self.lastNameFilter = "A"
            }

            Button("Show S") {
                self.lastNameFilter = "S"
            }
        }
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return ContentView().environment(\.managedObjectContext, context)
 
    }
}


Core Data、SwiftUI、@ FetchRequestとの1対多の関係
One-to-many relationships with Core Data, SwiftUI, and @FetchRequest

保存project名—>> CoreDataProject3 countryフェッチ版
保存project名—>> CoreDataProject3改 Candyフェッチ版

データモデルを開いて、「name」という文字列属性を持つCandyと、「fullName」および「shortName」という文字列属性を持つCountryの2つのエンティティを追加します。一部の種類のキャンディーは同じ名前を持っていますが(米国と英国の「Smarties」を参照)、国は完全に一意であるため、「shortName」の制約を追加してください。

CandyとCountryの間に1対多の関係があることをコアデータに通知する必要があります。

Core Data、SwiftUI、@ FetchRequestとの1対多の関係

import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Country.entity(), sortDescriptors: []) var countries: FetchedResults<Country>

    var body: some View {
        VStack {
            List {
                ForEach(countries, id: \.self) { country in
                    Section(header: Text(country.wrappedFullName)) {
                        ForEach(country.candyArray, id: \.self) { candy in
                            Text(candy.wrappedName)
                        }
                    }
                }
           
            }
            
            Button("Add") {
                let candy1 = Candy(context: self.moc)
                candy1.name = "Mars"
                candy1.origin = Country(context: self.moc)
                candy1.origin?.shortName = "UK"
                candy1.origin?.fullName = "United Kingdom"

                let candy2 = Candy(context: self.moc)
                candy2.name = "KitKat"
                candy2.origin = Country(context: self.moc)
                candy2.origin?.shortName = "UK"
                candy2.origin?.fullName = "United Kingdom"

                let candy3 = Candy(context: self.moc)
                candy3.name = "Twix"
                candy3.origin = Country(context: self.moc)
                candy3.origin?.shortName = "UK"
                candy3.origin?.fullName = "United Kingdom"

                let candy4 = Candy(context: self.moc)
                candy4.name = "Toblerone"
                candy4.origin = Country(context: self.moc)
                candy4.origin?.shortName = "CH"
                candy4.origin?.fullName = "Switzerland"

                try? self.moc.save()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return ContentView().environment(\.managedObjectContext, context)
 
    }
}

下は、Candyをフェッチした場合の改定版。上は、Countryをフェッチしたのもの。Candy、Countryはentityの名前。下のextensionは、リレーションによりXcodeが自動生成したもの。

Core Data、SwiftUI、@ FetchRequestとの1対多の関係

import Foundation
import CoreData

extension Candy {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Candy> {
        return NSFetchRequest<Candy>(entityName: "Candy")
    }

    @NSManaged public var name: String?
    @NSManaged public var origin: Country?
    
    public var wrappedName: String {
        name ?? "Unknown Candy"
    }
   
    public var wrappedoriginfullName: String {
        return  String(origin?.fullName ?? "Unknown Candy")
    }

}

import Foundation
import CoreData


extension Country {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Country> {
        return NSFetchRequest<Country>(entityName: "Country")
    }

    @NSManaged public var fullName: String?
    @NSManaged public var shortName: String?
    //@NSManaged public var candy: NSSet?
    @NSManaged public var candy: Set<Candy>?
    
    public var wrappedShortName: String {
        shortName ?? "Unknown Country"
    }

    public var wrappedFullName: String {
        fullName ?? "Unknown Country"
    }

    public var candyArray: [Candy] {
        //let set = candy as? Set<Candy> ?? []
        // fix version ↓
        let set = candy ?? []
        
        return set.sorted {
            $0.wrappedName < $1.wrappedName
        }
    }
}

// MARK: Generated accessors for candy
extension Country {

    @objc(addCandyObject:)
    @NSManaged public func addToCandy(_ value: Candy)

    @objc(removeCandyObject:)
    @NSManaged public func removeFromCandy(_ value: Candy)

    @objc(addCandy:)
    @NSManaged public func addToCandy(_ values: NSSet)

    @objc(removeCandy:)
    @NSManaged public func removeFromCandy(_ values: NSSet)

}
import Foundation
import CoreData


extension Country {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Country> {
        return NSFetchRequest<Country>(entityName: "Country")
    }

    @NSManaged public var fullName: String?
    @NSManaged public var shortName: String?
    //@NSManaged public var candy: NSSet?
    @NSManaged public var candy: Set<Candy>?
    
    public var wrappedShortName: String {
        shortName ?? "Unknown Country"
    }

    public var wrappedFullName: String {
        fullName ?? "Unknown Country"
    }

    public var candyArray: [Candy] {
        //let set = candy as? Set<Candy> ?? []
        // fix version ↓
        let set = candy ?? []
        
        return set.sorted {
            $0.wrappedName < $1.wrappedName
        }
    }
}

// MARK: Generated accessors for candy
extension Country {

    @objc(addCandyObject:)
    @NSManaged public func addToCandy(_ value: Candy)

    @objc(removeCandyObject:)
    @NSManaged public func removeFromCandy(_ value: Candy)

    @objc(addCandy:)
    @NSManaged public func addToCandy(_ values: NSSet)

    @objc(removeCandy:)
    @NSManaged public func removeFromCandy(_ values: NSSet)

}