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.
Related
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.
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())
}
}
}
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
}
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()
}
}
}
}
When working with nodejs, I like to use console.log to see what data is available in an object.
However, this doesn't work with inherited properties:
var Person = function () {};
Person.prototype.name = "anonymous";
var p = new Person();
console.log(['p', p]); // [ 'p', {} ]
// This doesn't even give me a hint that it's inherited from Person!
console.log(['typeof p', typeof p]); // [ 'typeof p', 'object' ]
console.log(['p.name', p.name]); // "anonymous"
Given an object, how can view all the properties I can access?
If your purpose is just for debugging, you can check the __proto__ object:
function Person() {};
Person.prototype.name = "abc";
Person.prototype.smallObj = {
name: "abc"
};
Person.prototype.deepObj = {
one: {
two: {
three: {
four: "4"
}
}
}
};
var p = new Person();
console.log(p);
// Person {}
console.log(p.__proto__);
/*
Person {
name: 'abc',
smallObj: { name: 'abc' },
deepObj: { one: { two: [Object] } }
}
*/
var util = require("util");
console.log(util.inspect(p.__proto__, {depth: null}));
/*
Person {
name: 'abc',
smallObj: { name: 'abc' },
deepObj: { one: { two: { three: { four: '4' } } } }
}
*/
On the last one, using util.inspect() with the depth option will allow you to look further into deeply nested objects.
You are assigning property to constructor function Person. It does not share properties with instances. You need to add property to Person's prototype:
Person.prototype.name = "anonymous";
To find out if your object inherited from Person you can do:
p instanceof Person; // true
You can print out all of an object's enumerable properties by performing the following:
for (var key in p) {
console.log(key);
}
Use Object.getOwnPropertyNames() to get all properties that belong to an object:
console.log(Object.getOwnPropertyNames(Person))
// [ 'length', 'name', 'arguments', 'caller', 'prototype' ]
console.log(Object.getOwnPropertyNames(Object))
// ['length','name','arguments','caller','prototype','keys','create', 'defineProperty','defineProperties','freeze','getPrototypeOf','setPrototypeOf','getOwnPropertyDescriptor','getOwnPropertyNames','is','isExtensible','isFrozen','isSealed','preventExtensions','seal','getOwnPropertySymbols','deliverChangeRecords','getNotifier','observe','unobserve','assign' ]
Also you can combine Object.getOwnPropertyNames() with walking up the prototype chain:
var getAllProperties = function (object) {
var properties = []
do {
Object.getOwnPropertyNames(object).forEach((prop) => {
if (!~properties.indexOf(prop)) {
properties.push(prop)
}
})
} while (object = Object.getPrototypeOf(object))
return properties
}