SwiftUI: How to show/edit an int from CoreData without being in a List? - core-data

self-learning beginner here. I am trying to show an Int from Core Data in a VStack in ContentView, not in a List. But literally all the tutorials I can find about Core Data (tracking Books, Movies, Orders, Students) are using a List to show an array containing an Int. Nothing on showing an Int by itself.
Xcode can build countnum.countnum +=1 with no problem. Seems to me it is reading it fine. But once I try to show it, it just doesn’t work. I’m wrecking my brain here.
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var countnum: FetchedResults<CountNum>
var body: some View {
// let countnum = CountNum(context: moc)
VStack{
Text("+")
.padding()
.onTapGesture (count: 2){
let countnum = CountNum(context: moc)
countnum.countnum += 1
}
Text("\(countnum)") //No exact matches in call to instance method 'appendInterpolation'
}
}
}
Thanks

....all the tutorials ... show an array containing an Int. Yes, that's because CoreData
can contain many "objects". You get an array of your CountNum objects when
you do your .....var countnum: FetchedResults<CountNum>. So you need to decide which CountNum you want to
use. For example, if you want to use the first one, then:
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var countnum: FetchedResults<CountNum>
var body: some View {
VStack {
if let firstItem = countnum.first {
Text("+")
.padding()
.onTapGesture(count: 2) {
firstItem.countnum += 1
do {
try moc.save()
} catch {
print(error)
}
}
Text("\(firstItem.countnum)").foregroundColor(.green)
}
}
}
}
EDIT-1: adding new CountNum to CoreData example code in the add button.
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var countnum: FetchedResults<CountNum>
var body: some View {
Button(action: {add()}) { Text("add new CountNum").foregroundColor(.green) }
.padding(.top, 50)
List {
ForEach(countnum) { item in
HStack {
Text("++")
.onTapGesture(count: 2) { increment(item) }
Text("\(item.countnum)").foregroundColor(.blue)
Text("delete").foregroundColor(.red)
.onTapGesture { delete(item: item) }
}
}
}
}
func increment(_ item: CountNum) {
item.countnum += 1
save()
}
func add() {
let countnum = CountNum(context: moc)
countnum.countnum = 0
save()
}
func delete(item: CountNum) {
moc.delete(item)
save()
}
func save() {
do { try moc.save() } catch { print(error) }
}
}

Related

How to use a function in another swift file that contains CoreData related properties?

clueless beginner here. Apologies if my question is formed poorly. This is the first time I ask a question of this scale and I have a hard time balancing between posting too much code for the good samaritans to read and too little to post an effective question. Huge thanks in advance!
I am trying to incorporate the textfieldalert in this post in my learner project. There are two Swift files in questions: File A (PosTextFieldAlertView) has an extension that needs to use two functions in File B (ListView).
These are the functions I need to use in File A.
func addPositive(){
let newPositive = PositiveEntity(context: viewContext)
newPositive.title = alertInput
save()
}
func save() {
do { try viewContext.save() } catch { print(error) }
}
I thought of/researched two methods: 1) duplicate the function in File A or 2) create instance of the view in File B that contains that functions according this post. However I ran into problems in both methods.
Duplicating the functions:
I copied the CoreData related properties in the PosTextFieldAlert struct. But now PosTextFieldAlert in the return part of the extension has the error of "Missing arguments for parameters [Core Data properties] in call". I don’t know how to set the property in the extension without referring or creating a different sets of Core Data entities.
Creating an instance of the relevant view
In the instance creation I would need to input the arguments but I don’t know how to refer to the same NSManagedObjectContext.
Code excerpts:
PosTextFieldAlertView
struct PosTextFieldAlert<Presenting>: View where Presenting: View {
var viewContext: NSManagedObjectContext
var positives: [PositiveEntity]
var targets: [TargetEntity]
#State private var alertInput = ""
// let listView = ListView(viewContext: NSManagedObjectContext, positives: PositiveEntity, negatives: NegativeEntity, targets: TargetEntity)
#Binding var isShowing: Bool
#Binding var text: String
let presenting: Presenting
let title: String
var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in
ZStack {
self.presenting
.disabled(isShowing)
VStack {
Text(self.title)
TextField(self.title, text: self.$text)
Divider()
VStack{
HStack {
Button(action: {
withAnimation {
self.isShowing.toggle()
}
}) {
Text("+")
}.padding()
Button(action: {
withAnimation {
self.isShowing.toggle()
}
}) {
Text("-")
}.padding()
}
Button(action: {
withAnimation {
self.isShowing.toggle()
}
}) {
Text("Done")
}
}
}
.padding()
.background(Color.white)
.frame(
width: deviceSize.size.width*0.7,
height: deviceSize.size.height*0.7
)
.shadow(radius: 1)
.opacity(self.isShowing ? 1 : 0)
}
}
}
func addPositive(){
let newPositive = PositiveEntity(context: viewContext)
newPositive.title = alertInput
save()
}
func save() {
do { try viewContext.save() } catch { print(error) }
}
}
extension View {
func posTextFieldAlert(isShowing: Binding<Bool>,
text: Binding<String>,
title: String) -> some View {
PosTextFieldAlert(isShowing: isShowing,
text: text,
presenting: self,
title: title)
}
}
The code in ListView
struct ListView: View {
var viewContext: NSManagedObjectContext
var positives: [PositiveEntity]
var negatives: [NegativeEntity]
var targets: [TargetEntity]
//[layout of the project]
}
The Fetchrequests in ContentView:
#Environment(\.managedObjectContext) var viewContext
#FetchRequest(sortDescriptors: []) var targets: FetchedResults<TargetEntity>
#FetchRequest(sortDescriptors: []) var positives: FetchedResults<PositiveEntity>
#FetchRequest(sortDescriptors: []) var negatives: FetchedResults<NegativeEntity>

onAppear is causing problem with the preview but no error is shown

self learning beginner here.
When I remove .onAppear{add()}, the preview works fine. I tried to attach it to other the body view, the Vstack but it causes another error. I read/watched several tutorials but nothing like this is mentioned....
Any help is appreciated
struct ListView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var targets: FetchedResults<TargetEntity>
#FetchRequest(sortDescriptors: []) var positives: FetchedResults<PositiveEntity>
var body: some View {
VStack {
Text("+")
.onAppear{add()}
.onTapGesture (count: 2){
do {
increment(targets.first!) //I also sense that doing "!" is not good. But it's the only way I can keep it from causing error "Cannot convert value of type 'FetchedResults' to expected argument type 'X'"
try moc.save()
} catch {
print("error")
}
}
}
}
func increment(_ item: TargetEntity) {
item.countnum += 1
save()
}
func add() {
let countnum = TargetEntity(context: moc)
countnum.countnum = 0
save()
}
func save() {
do { try moc.save() } catch { print(error) }
}
}
EDIT 20220509:
As advised by #Yrb (great thanks), the error is likely caused by the lack of a proper set up of preview var in the persistence file. I post the relevant code here for visiblity.
Data Controller file
import CoreData
import Foundation
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "CounterLateApr")
init () {
container.loadPersistentStores { description, error in
if let error = error {
print("Core Data failed to load: \(error.localizedDescription)")
}
}
}
}
preview code in a view
struct ListView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
ListView()
}
}
}
[AppName].app file
import SwiftUI
#main
struct CounterLateAprApp: App {
#StateObject private var dataController = DataController()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
}

Core Data: how to have a default value

self learning beginner here.
Although in the xcdatamodel file I have the default value for the attribute of that entity (see screenshot), when I build the preview, the Text("+") isn't there. I guess the countnum attribute in the TargetEntity is still empty. My thinking is to have the add() run if the attribute is empty. But that doesn't work either. Is there a way to automatically initialize the attribute when the app runs, instead of needing to build a button for the user to press?
Thanks a million
struct ListView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var targets: FetchedResults<TargetEntity>
var body: some View {
VStack {
if let firstItem = targets.first {
Text("+")
.onTapGesture (count: 2){
do {
increment(firstItem)
try moc.save()
} catch {
print(error)
add()
}
}
}
}
}
func increment(_ item: TargetEntity) {
item.countnum += 1
save()
}
func add() {
let countnum = TargetEntity(context: moc)
countnum.countnum = 0
save()
}
func save() {
do { try moc.save() } catch { print(error) }
}
}

Pass FetchedResults through NavigationLink

I have a two CoreData objects:
RoadTrip
StatePlate.
Each RoadTrip items holds an NSSet of StatePlate.
Screen 1 (TripList) shows a list of all RoadTrip items. Screen 2 (StateList) shows a list of all StatePlate items in associated with the RoadTrip that a user selects. Selecting a StatePlate item in Screen 2 will toggle a bool value associated with that item.
Even though I can show the data and can toggle the bool value of each StatePlate, I am not seeing an immediate change to the UI of the screen. The StatePlate should jump from Section to Section in Screen 2 when it's bool value is toggled.
How can I pass this FetchedObject correctly from Screen 1 to Screen 2 so the UI is binded with the data?
Screen 1 (TripList)
struct TripList: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: RoadTrip.entity(), sortDescriptors: []) var roadTripItems: FetchedResults<RoadTrip>
var body: some View {
List {
ForEach(roadTripItems, id: \.self) { trip in
NavigationLink(destination: StateList(trip: trip)
.environment(\.managedObjectContext, self.managedObjectContext)) {
TripRow(roadTrip: trip)
}
}
}
}
}
Screen 2 (StateList)
struct StateList: View {
#Environment(\.managedObjectContext) var managedObjectContext
var trip: RoadTrip
var plates: [StatePlate] {
trip.plateArray
}
var unseenPlates: [StatePlate] {
trip.plateArray.filter { !$0.hasBeenSeen }
}
var seenPlates: [StatePlate] {
trip.plateArray.filter { $0.hasBeenSeen }
}
var body: some View {
List {
if !unseenPlates.isEmpty {
Section(header: Text("Unseen Plates")) {
ForEach(unseenPlates, id: \.self) { plate in
StateRow(plate: plate)
}
}
}
if !seenPlates.isEmpty {
Section(header: Text("Seen Plates")) {
ForEach(seenPlates, id: \.self) { plate in
StateRow(plate: plate)
}
}
}
}
}
}
StateRow
struct StateRow: View {
#Environment(\.managedObjectContext) var managedObjectContext
#ObservedObject var plate: StatePlate
var body: some View {
Button(action: {
self.plate.hasBeenSeen.toggle()
try? self.managedObjectContext.save()
}) {
HStack {
Text(String(describing: plate.name!))
Spacer()
if plate.hasBeenSeen {
Image(systemName: "eye.fill")
} else {
Image(systemName: "")
}
}
}
}
}
Your trip as object is not changed when plate has changed, so even if it was observed UI was not refreshed.
Here is possible force-refresh approach.
struct StateList: View {
#Environment(\.managedObjectContext) var managedObjectContext
#ObservedObject var trip: RoadTrip // << make observed
// .. other code
and add handling for updated plate/s
StateRow(plate: plate)
.onReceive(plate.objectWillChange) { _ in
self.trip.objectWillChange.send()
}

after delete of item in SwiftUI List (backed by Core Data) getting a "Thread 1: EXC_BAD_INSTRUCTION" error? (code attached)

After completing a delete of a row in a SwiftUI List I am getting a "Thread 1: EXC_BAD_INSTRUCTION" error. It seems the Core Data delete works as after I restart that data has been removed. So maybe something to do with SwiftUI trying to update it's view after the Core Data delete is performed????
Code:
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(entity: GCItem.entity(), sortDescriptors: []) var gcItems: FetchedResults<GCItem>
private func addItem(title:String) {
let newItem = GCItem(context: context)
newItem.id = UUID()
newItem.title = title
do {
try context.save()
} catch let e as NSError {
fatalError("Unresolved error \(e), \(e.userInfo)")
}
}
private func deleteItem(at offsets:IndexSet) {
self.context.perform {
// Delete Item
for index in offsets {
let item = self.gcItems[index]
self.context.delete(item)
}
// Persist
do {
try self.context.save()
} catch let e as NSError {
// TODO: How to undelete list???
print("ERROR : Can not save GCItem items: \(e.description)")
}
}
}
var body: some View {
NavigationView {
VStack {
List() {
ForEach(gcItems) { gcItem in
HStack {
Text("test")
}
}
.onDelete(perform: self.deleteItem)
}
Button(action: { self.addItem(title: "Testing 123") }) {
Text("ADD ITEM")
}
}
}
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return ContentView().environment(\.managedObjectContext, context)
}
}
#endif
Screen Shot
The fix was NOT to use attributes in Core Data named "id". So I changed this to "myId" and then things worked fine.

Resources