I'm try to use my data that fetched from Core Data in nested list, but when I add the "children: .listofTasksArray" the following error is shown:
"Key path value type '[ListOfTasks]?' cannot be converted to contextual type 'FetchedResults?'"
this is the core data file
extension ListOfTasks {
#nonobjc public class func fetchRequest() -> NSFetchRequest<ListOfTasks> {
return NSFetchRequest<ListOfTasks>(entityName: "ListOfTasks")
}
#NSManaged public var addedDate: Date?
#NSManaged public var color: String?
#NSManaged public var favoriteIndex: Int16
#NSManaged public var icon: String?
#NSManaged public var id: UUID?
#NSManaged public var index: Int16
#NSManaged public var isArchived: Bool
#NSManaged public var isFavorite: Bool
#NSManaged public var isLocked: Bool
#NSManaged public var title: String?
#NSManaged public var isList: Bool
#NSManaged public var origin: ListOfTasks?
#NSManaged public var subLists: NSSet?
// The wrapped items
public var wrappedAddedDate: Date {
return addedDate ?? Date()
}
// To convert the color from "String" to "Color" type
public var wrappedColor: Color {
return Color(colorName: color ?? "blue")
}
public var wrappedIcon: String {
return icon ?? "ellipsis.circle.fill"
}
public var wrappedId: UUID {
return id ?? UUID()
}
public var wrappedTitle: String {
return title ?? "Unknown Title"
}
public var listofTasksArray: [ListOfTasks]? {
let set = subLists as? Set<ListOfTasks> ?? nil
return set?.sorted { // sort by index
$0.index > $1.index
}
}
}
and this is the list code that I used to fetch the data and use it in the list trying to make nested list using ListOfTasks property "listofTasksArray" as a child for the list.
struct ListsView: View {
#FetchRequest(entity: ListOfTasks.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ListOfTasks.index, ascending: true)], animation: .default) private var lists: FetchedResults<ListOfTasks>
var body: some View {
List(lists, children: \.listofTasksArray, rowContent: { Text($0.wrappedTitle) })
}
}
Types of data container are expected to be the same, so try to wrap FetchedResults into an array, like (might be some tuning needed do to optionals)
var body: some View {
List(Array(lists), children: \.listofTasksArray) {
Text($0.wrappedTitle
}
}
Related
When I try to do this, the model is stored in the NSManagedObjectContext if I use the context, and without it it throws an error, but I'm not expecting the same result.
Is there an easy way to implement this?
class WordDal: NSManagedObject {
#nonobjc public class func fetchRequest() -> NSFetchRequest<WordDal> {
return NSFetchRequest<WordDal>(entityName: "WordDal")
}
#NSManaged public var word: String?
#NSManaged public var uuid: UUID?
}
struct WordPresentation {
let word: String
let uuid: UUID
}
func mappingNSManagedObject(_ wordPresentation: WordPresentation) -> WordDal {
let model = WordDal()
model.uuid = wordPresentation.uuid
model.word = wordPresentation.word
return model
}
Consider to redesign your model to have computed property for the new wrapper type that transforms the property value to and from the wrapper value.
Implementing a computed property in a Swift Core Data model is often a clear, more intuitive way to achieve what you need.
Here is an example implementation:
struct WordPresentation {
let word: String
let uuid: UUID }
class WordDal: NSManagedObject {
#nonobjc public class func fetchRequest() -> NSFetchRequest<WordDal> {
return NSFetchRequest<WordDal>(entityName: "WordDal")
}
#NSManaged public var word: String?
#NSManaged public var uuid: UUID?
var wordPresentation : WordPresentation {
get {
return WordPresentation(word: self.word, uuid: self.uuid)
}
set {
self.word = newValue.name
self.uuid = newValue.id
}
}
}
I solved the problem like this (I don't know why I put it off and didn't understand right away):
class WordDal: NSManagedObject {
#nonobjc public class func fetchRequest() -> NSFetchRequest<WordDal> {
return NSFetchRequest<WordDal>(entityName: "WordDal")
}
#NSManaged public var word: String?
#NSManaged public var uuid: UUID?
}
struct WordPresentation {
let word: String
let uuid: UUID
}
func removeFromStorage(by uuid: UUID) {
getDataFromStorage { [weak self] objects in
guard let self = self else { return }
if let objectForRemove = objects.first(where: { $0.uuid == uuid }) {
self.coreDataStack.mainContext.delete(objectForRemove)
self.coreDataStack.saveContext(self.managedObjectContext)
}
}
}
I'm creating a presentation level model with UUID!
And I delete only on him himself UUID.
Now I can walk both ways.
I'm building a checklist app that uses core data. It has two main views:
Home: a list of Checklist entities using a #FetchRequest
Checklist: a child view of home that is passed a Checklist and stored in an #ObservedObject. A Checklist contains an array of Category entities, and each Category contains an array of Items.
However, when I delete an item in the Checklist view, it disappears momentarily and then reappears in the list, empty. If, however I navigate back to the parent view (Home) and into the view again, it's gone as expected, so somehow the view isn't updating as I expect.
I've experimented with how I pass the Checklist into the child view, using #ObservedObject and #StateObject, and a few other things but haven't gotten anything to work. I'm not sure if calling a separate #FetchRequest on the child view is the way to go (or how I'd do that...)
Here's an image of the issue: https://i.stack.imgur.com/9AaNu.jpg
Here's a simplified version of my code:
struct Home: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(entity: Checklist.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Checklist.lastModifiedDate, ascending: false)]) var checklists: FetchedResults<Checklist>
var body: some View {
NavigationView {
List {
ForEach(checklists, id: \.id){ checklist in
withAnimation {
ChecklistRowView(checklist: checklist)
}
}
.onDelete(perform: deleteChecklist)
}
}
}
private func deleteChecklist(offsets: IndexSet) {
withAnimation {
offsets.map { checklists[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
struct ChecklistRowView: View {
#ObservedObject var checklist: Checklist
var body: some View {
NavigationLink(destination: ChecklistView(checklist: checklist)){
HStack(spacing: 16){
Text(checklist.name ?? "")
}
}
}
}
struct ChecklistView: View {
#Environment(\.managedObjectContext) private var viewContext
#ObservedObject var checklist: Checklist
var body: some View {
List {
ForEach(checklist.categories) { category in
Section(header: Text(category.name!)) {
ForEach(category.items) { item in
Text("\(item.name ?? "")")
}
.onDelete { indexSet in
let deleteItem = category.items[indexSet.first!]
category.removeFromItems(deleteItem)
viewContext.delete(deleteItem)
do {
try viewContext.save()
} catch {
print(error)
}
}
}
}
}
}
private func saveState() {
do {
try viewContext.save()
print("saving")
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
extension Checklist {
#NSManaged public var createdDate: Date?
#NSManaged public var icon: String?
#NSManaged public var id: UUID?
#NSManaged public var lastModifiedDate: Date?
#NSManaged public var name: String?
#NSManaged public var categories: [Category]
}
extension Category {
#NSManaged public var id: UUID?
#NSManaged public var name: String?
#NSManaged public var checklist: Checklist?
#NSManaged public var items: [Item]
}
extension Item {
#NSManaged public var id: UUID?
#NSManaged public var isChecked: Bool
#NSManaged public var name: String?
#NSManaged public var categories: NSSet?
}
I was able to solve this! The issue is that the checklist can only see changes on its direct children. Because items are grandchildren to the Checklist, the UI won't re-render after one is deleted.
Checklist > Categories > Items
I was able to solve this by making each section its own view observing its specific Category.
struct ChecklistView: View {
#Environment(\.managedObjectContext) private var viewContext
#ObservedObject var checklist: Checklist
var body: some View {
List {
ForEach(checklist.categories) { category in
SectionView(category: category)
}
}
}
}
struct SectionView: View {
#Environment(\.managedObjectContext) private var viewContext
#ObservedObject var category: Category
var body: some View {
Section(header: Text(category.name!)) {
ForEach(category.items) { item in
Text("\(item.name ?? "")")
}
.onDelete {
// delete function goes here
}
}
}
}
Lets say i have an settings view in my app where a user can select 'Imperial' or 'Metric'. these settings are stored in userDefaults like so:
enum UserDefaultsKeys: String {
case measurementUnit = "measurementUnit"
}
enum MeasurementUnit: String, CaseIterable {
case metric = "metric"
case imperial = "Imperial"
}
class UserDefaultsWrapper: ObservableObject {
#Published var measurementUnit: String {
didSet {
UserDefaults.standard.set(measurementUnit, forKey: UserDefaultsKeys.measurementUnit.rawValue)
}
}
init() {
let locale = Locale.current
let systemMeasurementUnit = locale.usesMetricSystem ? MeasurementUnit.metric : MeasurementUnit.imperial
self.measurementUnit = UserDefaults.standard.string(forKey: UserDefaultsKeys.measurementUnit.rawValue) ?? systemMeasurementUnit.rawValue
}
}
struct SettingsView: View {
#StateObject var userDefaultsWrapper = UserDefaultsWrapper()
var body: some View {
Form {
HStack {
Text("Units \(userDefaultsWrapper.measurementUnit)")
Picker(selection: $userDefaultsWrapper.measurementUnit, label: Text("")) {
ForEach(MeasurementUnit.allCases, id: \.self) { unit in
Text(unit.rawValue)
.tag(unit.rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Settings")
}
}
Now the app can add workout sessions which i will always store in metric and convert to imperial if needed. If they create a workout in imperial i will first convert it to metric to store it and to show it i will convert it back to imperial values.
to store these workouts i use core data. i want to have a formattedMeasurement computed property in my coreDataProperties file like this:
extension WorkoutSession {
#nonobjc public class func fetchRequest() -> NSFetchRequest<WorkoutSession> {
return NSFetchRequest<WorkoutSession>(entityName: "WorkoutSession")
}
#NSManaged public var created_at: Date?
#NSManaged public var id: UUID?
#NSManaged public var updated_at: Date?
#NSManaged public var measurement: Double
override public func awakeFromInsert() {
super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: "id")
setPrimitiveValue(Date(), forKey: "updated_at")
}
var formattedMeasurement: String {
let userDefaultsWrapper = UserDefaultsWrapper()
let measurementUnit = MeasurementUnit(rawValue: userDefaultsWrapper.measurementUnit) ?? MeasurementUnit.metric
return measurementUnit == .metric ? // convert to metric : // convert to imperial
}
}
The settings tab and the workout tab are both accessible via a TabView.
Now if i change the measurement settings on the settings page and i go back to the workoutView the formattedMeasurement is not updated. only when i restart the app of force update that view by visiting another view outside the tabView.
I think this has something to do with the way classes work because i make 2 instances of the UserDefaulsWrapper class.
What is a better way of doing this?
I want to add a method or a custom attribute to the generate subclass Entity.
//---------------- Books+CoreDataClass.swift ----------
import Foundation
import CoreData
#objc(Books)
public class Books: NSManagedObject {
}
//--------------------------end file Books+CoreDataClass.swift —
//----------- Books+CoreDataProperties.swift
//
import Foundation
import CoreData
extension Books {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Event> {
return NSFetchRequest<Books>(entityName: "Books")
}
#NSManaged public var bookNumber: Int16
#NSManaged public var bookName: String?
#NSManaged public var chapterNumber: Int16
#NSManaged public var chapterName: String?
#NSManaged public var imageNumber: Int16
#NSManaged public var imageName: String?
#NSManaged public var description: String?
#NSManaged public var timestamp: NSDate?
// ---------When I was using Object-c, I used to have the following
//
// - (NSString *)sortingSectionAttribute
// {
// return [NSString stringWithFormat:#“Book: %# - Chapter: %#”, self.bookNumber, self. chapterNumber ];
// }
}
///-------------------end file Books+CoreDataProperties.swift ------
But I dont know how to do it in Swift. Though I tried many ways to accomplish this but it gives me an error when I run the app and hit the
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: “sortingSectionAttribute”, cacheName: nil)
I created the above method in swift and just added objc infront of the method declaration and it work as I want.
I have a subclass of NSManagedObject. I'm using a protocol to create a "wrapper" class. In my controller the data can be either: Items or Item1. To be able to use my function I'll have to add the protocol ItemInfo to Items but that means I'll have to add
var items: Items { return self }
in Items, which seems a bit redundant. I've tried creating a base class but that didn't work.
Question:
Is there a better way to let my function accept both Items and Item1 as parameter like using generics?
NSManagedObject:
class Items: NSManagedObject {
#NSManaged var name: String
#NSManaged var code: String
}
Protocol:
protocol ItemInfo {
var item: Items { get }
}
extension ItemInfo {
var name : String { return item.name }
var code : String { return item.code }
}
Wrapper:
class Item1: ItemInfo {
let item: Items
init(item: Items) { self.item = item }
}
function:
func handleItem(item: ItemInfo) {
print(item.name)
print(item.code)
}
I could use:
func handleItem<T>(item: T) {
if let a = item as? Items {
print(a.name)
print(a.code)
}
if let a = item as? ItemInfo {
print(a.name)
print(a.code)
}
}
But this doesn't seem the right way ...
If I understand correctly what you are trying to achieve (function accepting two kind of items), I would use protocol as type accepted by function, refer the code below
class Items: NSManagedObject, ItemInfo {
#NSManaged var name: String
#NSManaged var code: String
}
class Item1: NSManagedObject, ItemInfo {
#NSManaged var name: String
#NSManaged var code: String
}
protocol ItemInfo {
var name: String {get set}
var code: String {get set}
}
and function would look like this
func handle(item: ItemInfo) {
print(item.code)
print(item.name)
}