edit a Core Data Object in SwiftUI - core-data

I push an object that comes from the Core Data Database to a detail page with text fields.
When the user changes the text in the textfields and presses save the changes should be saved to the core data DB.
Problem: I have no clue how to modify/update/change an existing core data entity.
I probably need to get the original via a #FetchRequest but every time I try I get some problems.
Question 1: Let's say the entity has object.id as UUID, how can I
fetch that object in SwiftUI?
Question 2: How can I overwrite the fetched object with the changed
content of the textfields?
struct ProductDetail: View {
#State var barcode: Barcode
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) var context
//let datahandler = Datahandler()
var body: some View {
VStack {
HStack {
Text("Barcode: ")
Spacer()
TextField("Barcode", text: Binding($barcode.code, ""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, alignment: .trailing)
}
HStack {
Text("Amount: ")
Spacer()
TextField("Amount", text: Binding($barcode.amount, ""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, alignment: .trailing)
.keyboardType(.numberPad)
}
HStack{
Button("Back") {
self.presentationMode.wrappedValue.dismiss()
}
Spacer()
Button("Save"){
//self.datahandler.updateBarcode(barcode: self.object)
self.editBarcode(barcode: barcode)
self.presentationMode.wrappedValue.dismiss()
}
}
.padding()
}
.padding()
}
func editBarcode(barcode: Barcode) {
// Question 1: Fetch Original object using barcode.id
// Question 2: How to but barcode into context so it can overwrite the core data original?
try? context.save()
}
Attempt:
func editBarcode(barcode: Barcode) {
#FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "self.id IN %#", barcode.id)) var results: FetchedResults<Barcode>
results.first?.amount = barcode.amount
results.first?.code = barcode.code
try? context.save()
}
Errors:
Argument type 'UUID?' does not conform to expected type 'CVarArg'
Cannot use instance member 'barcode' within property initializer;
property initializers run before 'self' is available
Property wrappers are not yet supported on local properties

Related

Core Data, Problem with updating (duplicating instead)

I am new to Swift UI. Could you please help me with core data updating?
Here is the point of a problem:
I am building a WatchOS app. There are 3 Views there:
FirstView - a view with a button to Add a new Goal and a List of added Goals.
AddGoalView - appears after pressing Add new Goal.
RingView - a view with a Goal Ring (similar to activity ring mechanics) and all the data presented.
The point of the problem is the next:
After adding a new Goal everything is alright. The Data passes correctly from AddGoalView to the FirstView. I need only 2 items to be passed out of AddGoalView (One String and one Double).
Then, after pressing on the recently created Goal I appear on the Ring View. I successfully pass there 2 items (that String and Double I mentioned).
On the RingView I want to update the 3-rd Item (double) and send it back. So it can be updated on the FirstView.
the Result:
Instead of updating this 3-d Item it just seems to create a completely new Goal on the First View below the previous Goal. Photo
My Code (FirstView):
struct FirstView: View {
#FetchRequest (
entity:NewGoal.entity(),
sortDescriptors:[NSSortDescriptor(keyPath: \NewGoal.dateAdded, ascending: false)],
animation: .easeInOut )
var results:FetchedResults<NewGoal>
#State var showMe = false
var body: some View {
ScrollView{
VStack{
VStack(alignment: .leading){
Text("My Goals:")
NavigationLink(
destination: AddGoalView(),
isActive: $showMe,
label: {
Image(systemName: "plus")
Text("Set Money Goal")
})
Text("Recents:")
ForEach(results){ item in
VStack(alignment: .leading){
NavigationLink(
destination: RingView(GTitle: item.goalTitle ?? "", Sum: item.neededSum),
label: {
HStack{
Image(systemName: "gear")
VStack(alignment: .leading){
Text(item.goalTitle ?? "")
HStack{
Text("$\(item.yourSum, specifier: "%.f")") ///This item doesn't update
Text("/ $\(item.neededSum, specifier: "%.f")")
}
}
}
})
}
}
}
}
}
}
}
My Code (AddGoalView):
struct AddGoalView: View {
#State private var goalTitle = ""
#State private var showMe:Bool = true
#State private var neededSum:Double = 0.0
#State private var isFocusedNum = false
#Environment(\.managedObjectContext) var context
#Environment(\.presentationMode) var presentationMode
var body: some View {
ScrollView{
VStack (alignment: .leading, spacing: 6){
TextField("Goal Name...", text: $goalTitle)
HStack{
Text("$\(neededSum, specifier: "%.f")")
.overlay(
RoundedRectangle(cornerRadius: 9)
.stroke(isFocusedNum ? Color.red : Color.white, lineWidth: 1)
.opacity(1.0))
.focusable(true) { newState in isFocusedNum = newState}
.animation(.easeInOut(duration: 0.1), value: isFocusedNum)
.digitalCrownRotation(
$neededSum,
from: 0,
through: 100000,
by: 25,
sensitivity: .high)
}
Button(action: addGoal) {
Text("Add Goal")
}
.disabled(neededSum == 0.0)
.disabled(goalTitle == "")
.navigationTitle("Edit")
}
}
}
private func addGoal(){
let goal = NewGoal(context: context)
goal.goalTitle = goalTitle
goal.dateAdded = Date()
goal.neededSum = neededSum
do{
try context.save()
presentationMode.wrappedValue.dismiss()
}catch let err{
print(err.localizedDescription)
}
}
My Code (RingView Code):
struct RingView: View {
#State private var isFocusedSum = false
#State private var yournewSum:Double = 0.0
var goalItem: NewGoal?
var Sum:Double
var GTitle:String
#Environment(\.managedObjectContext) var context
#Environment(\.presentationMode) var presentationMode
#FetchRequest var results: FetchedResults<NewGoal>
init(GTitle: String, Sum: Double){
self.GTitle = GTitle
self.Sum = Sum
let predicate = NSPredicate(format:"goalTitle == %#", GTitle)
self._results=FetchRequest(
entity: NewGoal.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \NewGoal.dateAdded, ascending: false)],
predicate: predicate,
animation: .easeInOut
)
}
var body: some View {
ZStack{
ForEach(results) { item in
RingShape(percent:(yournewSum/item.neededSum*100), startAngle: -90, drawnClockwise: false) /// Ring
.stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round))
.fill(AngularGradient(gradient: Gradient(colors: [.red, .pink, .red]), center: .center))
.frame(width: 155, height: 155)
HStack(alignment: .top){
Spacer()
Button(action: addSum) { ///BUTTON TO Update
Image(systemName: "gear")
}
.clipShape(Circle())
}
VStack(alignment: .trailing, spacing: 0.0){
Spacer()
Text("$\(yournewSum, specifier: "%.f")") /// Here is the data I want to change via Digital Crown and update
.font(.title3)
.overlay(
RoundedRectangle(cornerRadius: 7)
.stroke(Color.white, lineWidth: 2)
.opacity(isFocusedSum ? 1.0:0.0)
)
.focusable(true) { newState in isFocusedSum = newState}
.animation(.easeInOut(duration: 0.3), value: isFocusedSum)
.digitalCrownRotation(
$yournewSum,
from: 0,
through: Double((item.neededSum)),
by: 10,
sensitivity: .high)
Text("/ $\(item.neededSum, specifier: "%.f")") ///Here is the Double data I entered in AddGoalView
.font(.caption)
}
.frame(width: 200, height: 230)
.padding(.top, 7)
VStack(alignment: .center, spacing: 1.0){
Text(item.goalTitle ?? "Your Goal Name") ///Here is the String data I entered in AddGoalView
.foregroundColor(.gray)
}
.padding(.top, 200.0)
}
}
.padding([.top, .leading, .trailing], 5.0)
}
private func addSum(){
let goal = goalItem == nil ? NewGoal(context: context): goalItem
goal?.yourSum = yournewSum //// I am trying to update the Data here, but after running the func it creates a duplicate.
do{
try context.save()
presentationMode.wrappedValue.dismiss()
} catch let err{
print(err.localizedDescription)
}
}
You never give var goalItem: NewGoal? the initial value of the item you want to update.
try replacing this
RingView(GTitle: item.goalTitle ?? "", Sum: item.neededSum)
with
RingView(goalItem: item, GTitle: item.goalTitle ?? "", Sum: item.neededSum)
and of course your have to change your initializer for RingView to
init(goalItem: NewGoal? = nil, GTitle: String, Sum: Double){
and add to the initializer this line
self.goalItem = goalItem

Core Data Count SwiftUI

When I add a new reminder to my list I can not see the count + 1 simultaneously. But when I re-run the program I see the count is correct.
https://vimeo.com/545025225
struct ListCell: View {
var list : CDListModel
#State var count = 0
#State var isSelected: Bool = false
var body: some View {
HStack{
Color(list.color ?? "")
.frame(width: 30, height: 30, alignment: .center)
.cornerRadius(15)
Text(list.text ?? "")
.foregroundColor(.black)
.font(.system(size: 20, weight: .regular, design: .rounded))
Spacer()
Text(String(count))
.foregroundColor(.gray)
.onAppear{
DispatchQueue.main.async {
self.count = list.reminders!.count
}
}
}
}
}
If CDListModel is a CoreData entity, then you can just add this:
#ObservedObject var list : CDListModel
Also remove the State for the count.
Then display the count like this:
Text(String(list.reminders!.count))
As hint: I wouldn't use force unwrapping aswell. Maybe provide a default value instead of force unwrapping

How avoid that when keyboard appear move the layout up in SwiftUI? [duplicate]

I am using TextField in my view and when it becomes the first responder, it lefts the view as shown in the below GIF.
Is there any way I can get rid of this behavior?
Here is my code
NavigationView(content: {
ZStack{
MyTabView(selectedIndex: self.$index)
.view(item: self.item1) {
NewView(title: "Hello1").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item2) {
NewView(title: "Hello2").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item3) {
NewView(title: "Hello3").navigationBarTitle("")
.navigationBarHidden(true)
}
}.navigationBarHidden(true)
.navigationBarTitle("")
}).ignoresSafeArea(.keyboard, edges: .bottom)
// New View
struct NewView:View {
#State var text:String = ""
var title:String
var body: some View {
VStack {
Spacer()
Text("Hello")
TextField(title, text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding()
.onAppear {
debugPrint("OnApper \(self.title)")
}
}
}
For .ignoresSafeArea to work you need to fill all the available area (eg. by using a Spacer).
The following will not work (no Spacers, just a TextField):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
However, it will work when you add Spacers (fill all the available space):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
Spacer()
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
If you don't want to use Spacers you can also use a GeometryReader:
struct ContentView: View {
#State var text: String = ""
var body: some View {
GeometryReader { _ in
...
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
You should apply the modifier on the ZStack, NOT the NavigationView
NavigationView(content: {
ZStack{
,,,
}.navigationBarHidden(true)
.navigationBarTitle("")
.ignoresSafeArea(.keyboard, edges: .bottom) // <- This line moved up
})
Full working example:
struct ContentView: View {
#State var text = ""
var body: some View {
VStack{
Spacer()
Text("Hello, World")
TextField("Tap to test keyboard ignoring", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
What eventually worked for me, combining answers posted here and considering also this question, is the following (Xcode 12.4, iOS 14.4):
GeometryReader { _ in
VStack {
Spacer()
TextField("Type something...", text: $value)
Spacer()
}.ignoresSafeArea(.keyboard, edges: .bottom)
}
Both spacers are there to center vertically the textfield.
Using only the GeometryReader or the ignoresSafeArea modifier didn't do the trick, but after putting them all together as shown above stopped eventually the view from moving up upon keyboard appearance.
That's what I figured out:
GeometryReader { _ in
ZStack {
//PUT CONTENT HERE
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
It seems to work for me. In this case you do not need to check iOS 14 availability.

SwiftUI handling SpriteKit scene recreation when data changes

I have a SwiftUI view that is connected to a CoreData model. I also have a SpriteKit scene that changes data in my model. So every time I manipulate my data from my SKScene in CoreData my scene gets reinitialised which is an unwanted behaviour in my case.
How can I get the updated model in my SpriteView without the SKScene being recreated?
My code looks like this:
struct TamagotchiListView: View {
#Environment(\.managedObjectContext)
var context: NSManagedObjectContext
#FetchRequest(fetchRequest: TamagotchiModel.getFetchRequest())
var tamagotchis: FetchedResults<TamagotchiModel>
var body: some View {
VStack {
List {
ForEach(tamagotchis, id: \.self) { (tamagotchi: TamagotchiModel) in
NavigationLink(destination: SpriteKitView(scene: SpriteKitScene(model: tamagotchi))) {
HStack {
Image(systemName: "gamecontroller")
.padding(.trailing, 5)
VStack(alignment: .leading) {
Text(tamagotchi.name)
.font(.headline)
Spacer()
Text(tamagotchi.birthDate, style: .date)
}
Spacer()
}
}
}
}
}
}
I managed to work around my problem by creating a view model that manages the SpriteKit scene creation if needed.
class TamagotchiViewModel {
private var spriteKitScenes: [SpriteKitScene] = []
func scene(for tamagotchi: TamagotchiModel) -> SpriteKitScene {
if let scene = spriteKitScenes.first(where: { $0.tamagotchi?.tamagotchiModel.id == tamagotchi.id}) {
return scene
} else {
let newScene = SpriteKitScene(model: tamagotchi)
spriteKitScenes.append(newScene)
return newScene
}
}
}

How do I use a property as an argument to a FetchRequest predicate

I am trying to select database items with a predicate before displaying a view and need to pass a property, but I'm in a catch-22 situation since the property may not be initialized, yielding the message: Cannot use instance member 'subject' within property initializer; property initializers run before 'self' is available
struct ShowInfo: View
{
#State var subject: Subject
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Info.entity(), sortDescriptors: [NSSortDescriptor(key: "title", ascending: true)],
predicate: NSPredicate(format: "subject.title == %#", $subject.title)
) var infos: FetchedResults<Info>
#State var size = UIScreen.main.bounds.width / 3
var body: some View
{
List
{
Text("Info").font(.system(size: 24)).foregroundColor(Color.green)
ForEach(infos, id: \.infoid)
{ info in
ZStack
{
if info.subject == self.subject.title
{
NavigationLink(destination: EditInfoView(info: info))
{
HStack
{
Text(info.title!).frame(width: 150, height: 40).background(Color.blue).foregroundColor(Color.white).cornerRadius(10)
Text(info.value!)
}
}
}
}
}.onDelete(perform: deleteInfo)
}
}
}
The answer found at SwiftUI View and #FetchRequest predicate with variable that can change is essentially correct, but I discovered that the order in which things appear inside the struct matters to avoid a slew of errors.
I modified my code as follows...
struct ShowInfo: View
{
init (subject: Subject)
{
self.subject = subject
self.infoRequest = FetchRequest<Info>(entity: Info.entity(), sortDescriptors: [NSSortDescriptor(key: "title", ascending: true)],predicate: NSPredicate(format: "subject.title == %#", subject.title!))
}
#Environment(\.managedObjectContext) var moc
var subject: Subject
var infoRequest: FetchRequest<Info>
var infos: FetchedResults<Info>{infoRequest.wrappedValue}
This cleared up all the errors and allowed the app to work correctly. When the init() was after the variable declarations, I got the same error as before as well as other errors. The compiler seemed to be getting confused about what had already been initialized and what had not. I'm not sure why the example shown for question 57871088 worked for others and mine required rearranging the declarations.
I thought the above answer solved my problem, and it did in that case, but in another view the same methodology yielded errors claiming that I hadn't initialized all the properties within the init. After much experimenting, I discovered all those errors went away if I commented out the #Environment statement. Of course that generated errors for the undefined variable moc, so I commented that code out to see what happened, then the original errors reappeared. It seems clear that the compiler is getting confused. Does anyone know a way around that issue?
Here's the code I'm working with now:
struct EditSubjectView: View
{
init(subject: Subject)
{
self.subject = subject
self.formRequest = FetchRequest(entity: Field.entity(), sortDescriptors: [NSSortDescriptor(key: "sequence", ascending: true)], predicate: NSPredicate(format: "subjectid == %#", subject.subjectid!.description))
}
#Binding var subject: Subject
var formRequest: FetchRequest<Field>
var fields : FetchedResults<Field>{formRequest.wrappedValue}
#Environment(\.managedObjectContext) var moc
var body: some View
{
return VStack
{
HStack
{
Text("Subject Title").padding(.leading)
//Spacer()
}
TextField(subject.title!, text: Binding($subject.title)!)
.padding(.all)
.background(Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0))
.cornerRadius(15)
Spacer()
Button("Save")
{
do
{
try self.moc.save()
}
catch
{
print(error.localizedDescription)
}
}.frame(width: 150, height: 30).background(Color.red).foregroundColor(Color.white).cornerRadius(15.0)
}.padding()
}
}

Resources