Why #FetchRequest fails in ContentView - core-data

I have following code in my SwiftUI ContentView
struct ContentView: View {
#State private var search = ""
#State private var selectedBookID: Int64? = 0
#FetchRequest(entity: Books.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Books.bokName, ascending: true)])
var books: FetchedResults<Books>
var body: some View {
NavigationView {
VStack {
HStack {
NavigationLink("New book...", destination: BookView(isNew: true)).padding(.leading)
Spacer()
NavigationLink("Authors...", destination: AuthorView())
NavigationLink("Genres...", destination: GenreView()).padding(.trailing)
}.padding(.vertical)
TextField("Search...", text: $search).padding(.horizontal)
List(books, id: \Books.bokID, selection: $selectedBookID) { book in
NavigationLink(book.bokName!, destination: BookView(isNew: false)).buttonStyle(PlainButtonStyle())
}
}
}
}
}
and it fails during app launching with error "No NSEntityDescriptions in any model claim the NSManagedObject subclass 'Books' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?".
It works quite fine for all other views in the same app.
Note it's macOS app.
Thanks.

I found the way - simply took #FetchRequest part out of ContentView and placed it into extra struct like below
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
HStack {
NavigationLink("New book...", destination: BookView(isNew: true)).padding(.leading)
Spacer()
NavigationLink("Authors...", destination: AuthorView())
NavigationLink("Genres...", destination: GenreView()).padding(.trailing)
}.padding(.vertical)
BookList()
}
}
}
}
struct BookList: View {
#State private var search = ""
#State private var selectedBookID: Int64? = 0
#FetchRequest(entity: Books.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Books.bokName, ascending: true)])
var books: FetchedResults<Books>
var body: some View {
TextField("Search...", text: $search).padding(.horizontal)
List(books, id: \Books.bokID, selection: $selectedBookID) { book in
NavigationLink(book.bokName!, destination: BookView(isNew: false)).buttonStyle(PlainButtonStyle())
}
}
}

Related

Observable Object to CoreData - Value of type 'NSSet?' has no subscripts

This was my ModelsConfig
class ModelsConfig: ObservableObject {
#Published var lists: [ListModel] = []
#Published var reminders: [Reminder] = []
}
and those were my models
struct ListModel: Hashable {
var color: String
var text: String
var reminders: [Reminder]
}
struct Reminder: Hashable {
var title: String
var notes: String
var date: Date
var index: Int
var list: ListModel
}
Before I was able to reach indices in this View as this with the help of ModelsConfig
struct ListDetailView: View {
#Binding var selectedIndex: Int
#State var isSelected: Bool = false
#EnvironmentObject var config : ModelsConfig
ForEach(config.lists[selectedIndex].reminders.indices, id: \.self) { reminderIndex in
HStack {
Button(action: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1){
deleteReminder(at: reminderIndex)
}
}, label: {
// ReminderCell(reminder: list.reminders[reminderIndex])
ReminderCell(reminder: config.lists[selectedIndex].reminders[reminderIndex])
})
}
.padding(.bottom)
}
Now, I am trying to reach the same indices with the help of core data as this
#FetchRequest(sortDescriptors: [])
var list: FetchedResults<CDListModel>
ForEach(list[selectedIndex].reminders.indices , id: \.self) { reminderIndex in
HStack {
Button(action: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1){
deleteReminder(at: reminderIndex)
}
}, label: {
ReminderCell(reminder: list[selectedIndex].reminders[reminderIndex])
})
}
.padding(.bottom)
}
But it does not allow me to do so. How can I reach to indices inside the coredata?
In Core Data relationships are (NS)Sets. For performance reasons they are unordered.
The easiest solution is to convert the set to an array
ForEach((list[selectedIndex].reminders.allObjects as! [CDReminder]).indices , id: \.self) { reminderIndex in
Consider to declare the relationship as native Set<CDReminder>. Swift Sets are a sequence and got indices.

Search Text Does not Have Response in Predicate SwiftUI

searchText changes do not change predicate input, hence whatever I type inside the search bar I always get all of the reminders inside core data.
I want my list to change according to text typed inside the search bar.
Here is my SearchView
struct SearchView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest var reminder: FetchedResults<CDReminder>
#State var searchText: String = ""
init(searchText: State<String>) {
self._searchText = searchText
var predicate : NSPredicate?
if !self._searchText.wrappedValue.isEmpty{
predicate = NSPredicate(format: "name CONTAINS %#", self._searchText.wrappedValue)
}
self._reminder = FetchRequest(
entity: CDReminder.entity(),
sortDescriptors: [],
predicate: predicate
)
}
var body: some View {
VStack {
SearchBar(text: searchText)
List {
ForEach(self._reminder.wrappedValue.filter({
self.searchText.isEmpty ? true :
$0.notes!.localizedCaseInsensitiveContains(self.searchText)
}), id: \.self){ reminder in
DatedReminderCell(reminder: reminder, isSelected: false,
onComplete: {})
}
}
}
}
}
And my searchBar
struct SearchBar: View {
#State var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search", text: $text)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
}
}
}
)
.onTapGesture {
self.isEditing = true
}
}
}
}
I have created another View called FilteredList. In this View, I fetched all the reminders and then filtered them with a predicate.
import SwiftUI
import CoreData
struct FilteredList: View {
var fetchRequest: FetchRequest<CDReminder>
var reminders: FetchedResults<CDReminder> {fetchRequest.wrappedValue}
var body: some View {
List(fetchRequest.wrappedValue , id: \.self) { reminder in
DatedReminderCell(reminder: reminder, isSelected: false, onComplete: {})
}
}
init(filter: String) {
fetchRequest = FetchRequest<CDReminder>(
entity: CDReminder.entity(),
sortDescriptors: [],
predicate: NSPredicate(format: "title CONTAINS %#", filter.lowercased())
)
}
}
Then, I updated my SearchView with FilteredList instead of using List { ForEach statement. so the body of my SearchView is now like this.
var body: some View {
VStack{
SearchBar(text: $searchText)
.environment(\.managedObjectContext, viewContext)
FilteredList(filter: searchText)
}
}
So right now, when I enter text inside the search bar I see related reminders simultaneously.

SwiftUI - Custom Identifier with Identifiable Protocol

I'm wondering whether it's possible to override the standard identifier with a custom one.
I've a simple struct:
struct MyUserData: Identifiable {
var userId: String
var userFirstName: String
var userLastName: String
}
However, the Identifiable protocol wont work without var id: ObjectIdentifier row inside the struct. At the same time, I don't want to use the "id" name. userId is also unique in my model (its a UUID). Is there a way to tell the identifiable protocol to accept "userId" instead of "id"?
Thanks!
You can use any Hashable as an id property required by Identifiable:
extension MyUserData: Identifiable {
var id: String { userId }
}
Unfortunately Identifiable requires a property called id. The easiest way to solve it is to use a computed property called id to your struct.
struct MyUserData: Identifiable {
var userId: String
var userFirstName: String
var userLastName: String
var id: String {
userId
}
}
I think you need this one:
import SwiftUI
struct MyUserData
{
var userId: String
var userFirstName: String
var userLastName: String
}
struct ContentView: View {
#State var arrayOfMyUserData: [MyUserData] = [MyUserData]()
var body: some View {
List
{
ForEach(arrayOfMyUserData, id: \.userId) { item in
Text("FirstName: " + item.userFirstName) + Text(" LastName: " + item.userLastName)
}
}
.onAppear() {
arrayOfMyUserData = [MyUserData(userId: "userId1", userFirstName: "willy", userLastName: "will"),
MyUserData(userId: "userId2", userFirstName: "bob", userLastName: "mccarthy"),
MyUserData(userId: "userId3", userFirstName: "james", userLastName: "rodriguez")
]
}
}
}
Or you can use this:
struct MyUserData
{
let userId: UUID = UUID()
var userFirstName: String
var userLastName: String
}

What am i doing wrong here, My UI should be refreshing when a CoreData Object is updated

I'm working on SwiftUI, With CoreData, I have Group.entity that can have many Items.entity.
Clicking on group navigates to a list of items in said group. you click the star to Toggle its fill, like with a "Favorites"
my system works 100% however to see the change i need to navigate BACK out and in again.
from my research - CoreData Objects work like observable objects, and FetchRequests are LIVE as in they tell a view that data has change and its time to refresh the LIST or the FOREACH.
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Group.entity(), sortDescriptors: [])
var groups: FetchedResults<Group>
#FetchRequest(entity: Item.entity(), sortDescriptors: [])
var items: FetchedResults<Item>
var body: some View {
NavigationView {
VStack {
AddDummyDataButton()
List {
ForEach(groups, id: \.self) { group in
NavigationLink(destination: GroupDetail(group: group)) {
Text(group.wrappedName)
}
}.onDelete { IndexSet in
let deleteItem = self.groups[IndexSet.first!]
self.moc.delete(deleteItem)
try? self.moc.save()
}
}
}
}
}
}
struct GroupDetail: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Group.entity(), sortDescriptors: [])
var groups: FetchedResults<Group>
#FetchRequest(entity: Item.entity(), sortDescriptors: [])
var itemsFetchedggr: FetchedResults<Item>
var group: Group
var body: some View {
List {
ForEach(self.group.itemsArray, id: \.self) { item in
HStack {
Text(item.wrappedName)
Image(systemName: item.isFave ? "star.fill" : "star")
.foregroundColor(.yellow)
.onTapGesture {
item.isFave.toggle()
try? self.moc.save()
}
}
}
}
}
}
So - Calve off the Item into new struct for ItemRow - i guess this is best practice anyways, and use a for each to return the ItemRow passing in an Item.
this is pretty similar to the Landmarks Tutorial var item: Item
however we also need to make it :
#ObservedObject var item: Item.
I guess this is how swiftUI and CoreData are meant to work together, it took me ages to find a solution so i guess this will help someone in the future
full code below
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Group.entity(), sortDescriptors: [])
var groups: FetchedResults<Group>
var body: some View {
NavigationView {
VStack {
AddDummyDataButton()
List {
ForEach(groups, id: \.self) { group in
NavigationLink(destination: GroupDetail(group:
group)) {
Text(group.wrappedName)
}
}.onDelete { IndexSet in
let deleteItem = self.groups[IndexSet.first!]
self.moc.delete(deleteItem)
try? self.moc.save()
}
}
}
}
}
}
struct GroupDetail: View {
#Environment(\.managedObjectContext) var moc
var group: Group
var body: some View {
List {
ForEach(self.group.itemsArray, id: \.self) { item in
ItemRow(item: item)
}
}
}
}
struct ItemRow: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var item: Item
var body: some View {
HStack {
Text(item.wrappedName)
Image(systemName: self.item.isFave ? "star.fill" : "star")
.foregroundColor(.yellow)
.onTapGesture {
self.item.isFave.toggle()
try? self.moc.save()
}
}
}
}

What is the ideal way to display a datatable inside a YUI View

Given a YUI app with two views, HomePageView & tradedeskPageView, I want to display a datatable in the tradedeskview. TradedeskView is defined with a template like,
<script id="t-tradedesk" type="text/x-handlebars-template">
<div id="my-datatable"></div>
</script>
And the view is defined as below,
YUI().add('tradedeskPageView', function(Y) {
Y.TradedeskPageView = Y.Base.create('tradedeskPageView', Y.View, [], {
// Compiles the tradedeskTemplate into a reusable Handlebars template.
template: Y.Handlebars.compile(Y.one('#t-tradedesk').getHTML()),
initializer: function () {
var cols = [
{ key: 'id', label: 'ID' },
{ key: 'name', label: 'Name' }
];
var data = [
{ id: 12345, name: 'Srikanth'},
{ id: 12346, name: 'Aditya'}
];
this.table = new Y.DataTable({
columns: cols,
data: data
});
//this.table.render('#my-datatable'); This is not possible as view is not rendered and node with given id wont exist
},
render: function () {
var content = this.template();
this.get('container').setHTML(content);
return this;
}
});
}, '1.0.0', {
requires: []
});
How should I render table in the required div i.e., #my-datatable in this case.
Give this a go:
render: function () {
var content = this.template(),
container = this.get('container');
container.setHTML(content);
this.table.render(container.one('#my-datatable')); //node exists at this point
return this;
}
You can init the DataTable in you view's initializer, but only render it when the View itself is rendered

Resources