is there a function combin NSSet and FetchResult<> - ios

I am coding in Coredata , There are two entity(Message and Keyword) with an one to many relationship.
first , I creat three Keyword instance using #Environment(.managedObjectContext) var moc.
sec, I creat one Message instance using #Environment(.managedObjectContext) var moc
third, I want add three Keywords into the message but faild, the keywords in message is NSSet,
the message is FetchResults, how it can work, Ths.
extension Message {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Message> {
return NSFetchRequest<Message>(entityName: "Message")
}
#NSManaged public var id: UUID?
#NSManaged public var content: String?
#NSManaged public var creatAt: Date?
#NSManaged public var user: User?
#NSManaged public var keywords: NSSet?
#NSManaged public var photoes: NSSet?
}
extension Keyword {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Keyword> {
return NSFetchRequest<Keyword>(entityName: "Keyword")
}
#NSManaged public var id: UUID?
#NSManaged public var title: String?
#NSManaged public var message: Message?
}
.navigationBarTitle("Send Message", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
}) {
Text("Send")
.onTapGesture {
let newMessage = Message(context: self.moc)
newMessage.keywords = self.keywords
})
}

Your Message class should have a generated method named addToKeywords that takes a Set as an argument
newMessage.addToKeywords(self.keywords)

Related

SwiftUI How to get changes of computed Property of NSManagedObject subclass

I am using a List to display a CoreData one-to-many relationship models. The list displayed the computed property. When the NSManagedObject was changed, the computed property doesn't know about it.
Let's say People has many Books. Like the code below:
People+CoreDataProperties.swift
extension People {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Contract> {
return NSFetchRequest<People>(entityName: "People")
}
#NSManaged public var peopleID: String?
#NSManaged public var name: String?
#NSManaged public var books: Set<Book>?
#NSManaged public var bookOrders: [String]
}
The bookOrders is the ordered id of the books.
BookListView.swift
struct BookListView: View {
#state var people: People //this is from #FetchRequest
List {
ForEach(people.sortedBooks.indices, id:\.self) { index in
BookRowView(bookListItem: viewModel.books[index])
}
}
}
Here is the extension of People:
extension People {
var sortedBooks: [Book] {
let contractMapping = books.reduce(into: [String: Book]()) { (result, book) in
result[book.bookID] = book
}
return bookOrders.map {
contractMapping[$0]
}.compactMap { $0 }
}
So when I reorder the books, which say update the bookOrders or there's a background request update of adding the new books then how can I notify BookListView update the view?
Inside the BookListView declare your People as ObservedObject
struct BookListView: View {
#ObservedObject var people: People // << now View updates for any changes here

Filter Array by "isComplete"

I have two data sets "ProjectItem" and "TaskItem", and a project can have many tasks. I want to filter tasks by "isComplete" in the project they belong to.
In my ProjectItem+CoreDataProperties file I have the following:
extension ProjectItem {
#NSManaged public var projectColor: String
#NSManaged public var projectId: UUID
#NSManaged public var projectTitle: String
#NSManaged public var projectDateCreated: Date
#NSManaged public var isFavorite: Bool
#NSManaged public var task: NSSet
public var taskArray: [TaskItem] {
let set = task as? Set<TaskItem> ?? []
-- How can I filter for "isComplete" here? --
}
}
And TaskItem+CoreDataProperties looks like this:
extension TaskItem {
#NSManaged public var completedDate: Date
#NSManaged public var completeIcon: String
#NSManaged public var createdDate: Date
#NSManaged public var dueDate: Date
#NSManaged public var id: UUID
#NSManaged public var isComplete: Bool
#NSManaged public var notes: String
#NSManaged public var priority: String
#NSManaged public var title: String
#NSManaged public var project: ProjectItem?
}
How can I modify the array in the first code snippet to show only tasks where "isComplete" = true?
Many thanks!
You don't need to define the Coredata managed property with dynamic NSSet any more. You could as well use, generic set with the Element type and Core data is able to infer the type from underlying store. So, your class could be changed to something like this,
extension ProjectItem {
#NSManaged public var projectColor: String
#NSManaged public var projectId: UUID
#NSManaged public var projectTitle: String
#NSManaged public var projectDateCreated: Date
#NSManaged public var isFavorite: Bool
// Notice this
#NSManaged public var task: Set<TaskItem>
}
So, for completed task items, you can simply use filter on Set if you want.
extension ProjectItem {
var completedItems: Set<TaskItem> {
return task.filter(\.isComplete) // for 5.2 and above
}
}
It is more optimal to create explicit fetch request and allow CoreData to filter isCompleted tasks by predicate, like
struct TaskView: View {
var tasksRequest : FetchRequest<TaskItem>
var tasks : FetchedResults<TaskItem>{tasksRequest.wrappedValue}
init(){
self.tasksRequest = FetchRequest(entity: TaskItem.entity(), sortDescriptors: [],
predicate: NSPredicate(format: "isComplete == YES"))
}
// ... other code

Usage of LazyMapSequence when an array of Strings is excepted

I have a User object
#objc(User)
public class User: NSManagedObject {
#NSManaged public var firstname: String
#NSManaged public var lastname: String
#NSManaged public var country: String
#NSManaged public var friends: NSSet // of User objects
var full: String {
firstname + " " + lastname
}
var friendsArray: [User] {
friends.allObjects as? [User] ?? []
}
}
and at some point I want to map a large array of users (80k objects) to an array of View models
struct ItemViewModel: Hashable {
let id: UUID
let friendsName: [String]
}
Without lazy it takes a long time, so I have opted for the usag of lazy:
func prepareViewModel(users: [User]) -> [ItemViewModel] {
users.map { user in
let friendsName = user.friendsArray.lazy.filter{["en", "fr"].contains($0.country)}.map(\.full)
return ItemViewModel(id: UUID(), friendsName: friendsName)
}
}
But I get an error:
Cannot convert value of type 'LazyMapSequence<LazyFilterSequence<LazySequence<[User]>.Elements>.Elements, String>'
(aka 'LazyMapSequence<LazyFilterSequence<Array<User>>, String>') to expected argument type '[String]'
It makes sense because now the friends names array will be processed lazily later. I have tried to convert the view model struct to hold:
struct ItemViewModel: Hashable {
let id: UUID
let friendsName: LazyMapSequence<LazyFilterSequence<[User]>, String>
}
But now it's not Hashable is there a way to keep the auto-conformance to Hashable when using LazyMapSequence<LazyFilterSequence<[User]>, String> as type for ItemViewModel and any tips on how to improve performance of logic

Using Identifiable set of objects in List View in SwiftUI

I have a DataBase handled by DataCore. I am trying to retrieve any object of "assignment" and insert it to a View List. The assignment class itself is identifiable but I am getting an error while trying to create the List in the View :
Initializer 'init(_:id:rowContent:)' requires that 'Set<NSManagedObject>' conform to 'RandomAccessCollection'
Is the set itself is not identifiable even though the objects are identifiable ? How can I present all the objects in the set inside the View's list ?
The view:
import SwiftUI
struct AssignmentList: View {
#ObservedObject var assignmentViewModel = assignmentViewModel()
var body: some View {
VStack {
NavigationView {
//**The error is in the following line : **
List(assignmentViewModel.allAssignments, id: \.self) { assignment in
AssignmentRow(assignmentName: assignment.assignmentName, notes: assignment.notes) //This view works by itself and just present the data as text under HStack
}
.navigationBarTitle(Text("Assignments"))
}
Button(action: {
self.assignmentViewModel.retrieveAllAssignments()
}) {
Text("Retrieve")
}
}
}
}
This is the assignment class:
import Foundation
import CoreData
extension Assignment: Identifiable {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Assignment> {
return NSFetchRequest<Assignment>(entityName: "Assignment")
}
#NSManaged public var id: UUID?
#NSManaged public var assignmentName: String?
#NSManaged public var notes: String?
}
This is the ViewModel that connects to the view using binding:
class AssignmentViewModel : ObservableObject
{
private var assignmentModel = AssignmentModel()
/*AssignmentModel is a different class, we assume all methods working correctly and it's not a part of the question.*/
#Published var allAssignments : Set<Assignment>
init()
{
allAssignments=[]
}
func retrieveAllAssignment()
{
allAssignments=assignmentModel.retrieveAllAssignments()
}
}
Try the following
List(Array(assignmentViewModel.allAssignments), id: \.self) { assignment in

swift core data relationship one to many for own entity

Hi I'm building a newspaper database which have item relationship to itself (item can have many other item as child)
here is my NSmanageObjectSubclass
import Foundation
import CoreData
extension Item {
#NSManaged var by: String?
#NSManaged var dead: NSNumber?
#NSManaged var descendants: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var isdeleted: NSNumber?
#NSManaged var score: NSNumber?
#NSManaged var text: String?
#NSManaged var time: NSDate?
#NSManaged var title: String?
#NSManaged var type: String?
#NSManaged var url: String?
#NSManaged var kids: NSSet?
#NSManaged var parent: Item?
#NSManaged var parts: NSSet?
}
Problem is that how can i add item to property kids: NSSet
the relationship define is one to many to its own with reverse. Any help is much appreciate. Thanks.
my coredata xcmodel
First update your class Managed Object class to use Sets instead of NSSet
import Foundation
import CoreData
extension Item {
#NSManaged var by: String?
#NSManaged var dead: NSNumber?
#NSManaged var descendants: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var isdeleted: NSNumber?
#NSManaged var score: NSNumber?
#NSManaged var text: String?
#NSManaged var time: NSDate?
#NSManaged var title: String?
#NSManaged var type: String?
#NSManaged var url: String?
#NSManaged var kids: Set<Item>
#NSManaged var parent: Item?
#NSManaged var parts: NSSet?
}
Secondly create a fetchRequest named getItemByID with expression
id == $ID
Third create a function to get item by id
func getItemById(ID:String) -> Item?
{
let fetchRequest : NSFetchRequest = self.managedObjectModel.fetchRequestFromTemplateWithName("getItemByID", substitutionVariables: ["ID": ID])!
do {
let fetchedResult = try managedObjectContext!.executeFetchRequest(fetchRequest) as! [Item]
if fetchedResult.count > 0 {
let result = fetchedResult[0]
return result
}
}
catch {
print("Could not fetch \(error)")
}
return nil
}
After that you have many options to set relations
a) you create relation after adding parent & child to coreData
func addChildToParent(childID:String,parentID:String)
{
if let childItem = getItemById(childID)
{
if let parentItem = getItemById(parentID)
{
parentItem.kids.insert(menuItemCatering)
childItem.parent = parentItem
}
}
saveContext()
}
Try this to automatically create relationships and properties.
Generating Swift models from Core Data entities
i.e.
Edit: I found the solution to generate a Swift model from Core Data entity:
On Xcode:
Editor > Create NSManagedOjbect > Click button "Next" > Click button
"Next" > Select "Swift" Langage > Click button "Create"
Here is the link on how to create a relationship from scratch. This is a very well explained and awesome tutorial. Go through this and you'll be able to understand all the things.
http://code.tutsplus.com/tutorials/core-data-from-scratch-relationships-and-more-fetching--cms-21505

Resources