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
Related
I'm trying to make a Storage Expiration Notification APP.
I create a class called Product, here are the properties.
#NSManaged public var productName: String?
#NSManaged public var quantity: String?
#NSManaged public var category = ["", "Food", "Daily", "Makeup"]
#NSManaged public var chooseCategory: Int16
#NSManaged public var purchaseDate: String?
#NSManaged public var expiredDate: String?
#NSManaged public var productID: String?
But there's an Error showed that #NSManaged property cannot have an initial value
Hence, I only can move the Category array(use picker controller to choose the values) to ViewContorller.swift. But I want to create a Customize Category array that users can change the value. For instance, the default category is ["Food", "Daily", "Makeup"], users can change the value to ["Drink", "Wine", "Battery"]. Should I use archiving, or create a new class? I have no idea how to implement it.
The Core Data way to do this is by overriding awakeFromInsert. That function is inherited from NSManagedObject and is called once when the object is first inserted into a managed object context. For this case it would look something like
func awakeFromInsert() {
category = ["", "Food", "Daily", "Makeup"]
}
It doesn't work in exactly the same way as a Swift initial value but it has the same effect since it happens when you create a new instance.
The error is right, an #NSManaged property cannot have an initial value.
A lightweight Swift solution is a JSON string attribute and a computed property for the conversion.
#NSManaged public var category : String
and
var categoryArray : [String] {
get { (try? JSONDecoder().decode([String].self, from: Data(category.utf8))) ?? [] }
set {
let data = try! JSONEncoder().encode(newValue)
category = String(data: data, encoding: .utf8)!
}
}
Set the default value
"[\"\",\"Food\",\"Daily\",\"Makeup\"]
either in awakeFromInsert or in Interface Builder in the model
I am working on a classified ads app (something like craigslist) I am looking at how best to structure the data model using Firestore (NoSQL Database).
The issue I'm facing is structuring items that have additional fields. For instance, electronic items will have additional fields such as RAM, Storage, etc.
I currently have the following model
enum Category: Codable {
case standard
case phone(Phone)
case tv(TV)
}
struct Item: Codable {
var document_ID: String? = nil
var title: String
var created_at: Int
var category: Category
}
struct Phone: Codable {
var brand: String
var model: String
var RAM: String
var storage: String
}
struct TV: Codable {
var size: Int
var screen_technology: String
}
Although I am not sure this is the best way as the category object is being mapped too many times.
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
I have a BehaviorRelay with an Array of FamilyTaskCoreData inside it. In FamilyTaskCoreData I have the "owner" parameter and I want to filter the Array where it has the id of "45332523dqwd" or an other query.
This is my BehaviorRelay:
private var familyTask = BehaviorRelay<[FamilyTasksCoreData]>(value: [])
And this is the code I use to bind it:
let item = memberData.getTaskData(memberID: queryID)
item
.filter(
$0.filter{ $0.name.hasPrefix("M")}
)
.bind(to: tableView.rx.items(cellIdentifier: "familyCleaningPlanCell", cellType: FamilyCleaningPlanTableViewCell.self)) {[weak self] (row, element, cell) in
cell.titleLabel.text = element.title
cell.checkMarcButton.isSelected = element.status
cell.categoryImage.image = self?.defineImage(name: element.category ?? "")
self?.updateAnItem(cell: cell, data: element)
}.addDisposableTo(disposeBag)
}
I tried to filter it with the filter statement...because I saw it on another question, but I can't find something after $0.. in my case there is no value which I can select.
FamilyTasksCoreData:
#NSManaged public var category: String?
#NSManaged public var end: Date
#NSManaged public var id: String?
#NSManaged public var start: Date
#NSManaged public var status: Bool
#NSManaged public var title: String?
#NSManaged public var createdAt: Date
#NSManaged public var owner: String?
#NSManaged public var familyID: String?
If I understand correctly, you want the tableView to only show FamilyTasksCoreData objects that have a name beginning with "M".
To do that, you need to use a map on the relay instead of a filter. If you use a filter on the relay, you will filter out the entire array all at once, instead of the individual elements.
Instead, you want to use map to transform each array you get from the relay, filtering out the elements that do not begin with "M".
Your code should look something like this:
item
.map {
$0.filter { $0.name.hasPrefix("M") }
}
.bind(...
You need to use single filter to filter your array
item.filter{ $0.owner.hasPrefix("M")}
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)