SwiftUI OnDelete crashes using indices and bindings - ios

I'm building an app where you have cards for each study item that is stored in an array and what I wanted to do was to allow the user to swipe left on a study item and allow them to delete the card.
Each card has two bindings to allow user to edit two textFields, so I have to use indices on the ForEach to specify the textField that is being edited, because when I don't use indices and have the foreach parameter as a binding and iterate through each item, when the user edits a specify textField, he can only type in one letter and it skips to the next textField.
In this case, present below, I am able to swipe left on each card("Section") and delete it when it's empty but when I start typing on individual textFields with different texts and delete it, it crashes.
Any help will be appreciated!
Here is the foreach loop that is in a subview of another view
#ObservedObject var currentStudySet: HomeViewModel
ForEach(currentStudySet.studySet.studyItem.indices, id: \.self) { index in
Section {
VStack {
TextField("Title", text: $currentStudySet.studySet.studyItem[index].itemTitle)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(backgroundColor)
.cornerRadius(10)
TextField("Description", text: $currentStudySet.studySet.studyItem[index].itemDescription)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(backgroundColor)
.cornerRadius(10)
}
.frame(maxWidth: .infinity)
}
}
.onDelete(perform: { (item) in
currentStudySet.studySet.studyItem.remove(atOffsets: item)
})
The view model has studySet but I won't put it for legibility purposes but here is the StudyModel that is "studySet"
struct StudyModel: Hashable{
var title: String = ""
var days = ["One day", "Two days", "Three days", "Four days", "Five days", "Six days", "Seven days"]
var studyGoals = "One day"
var studyItem: [StudyItemModel] = []
}
Here is the studyItemModel which is the studyItem array in StudyModel
struct StudyItemModel: Hashable{
var itemTitle: String = ""
var itemDescription: String = ""
}

You can avoid using the index by doing the model conforms to Identifiable, this is the way the ForEach is meant to be use in SwiftUI atleast
import Foundation
import SwiftUI
struct fortest: View {
#ObservedObject var currentStudySet: HomeViewModel = HomeViewModel()
var body: some View {
List {
ForEach($currentStudySet.studySet.studyItem) { $studyItem in
Section {
VStack {
TextField("Title", text: $studyItem.itemTitle)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray)
.cornerRadius(10)
TextField("Description", text: $studyItem.itemDescription)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray)
.cornerRadius(10)
}
.frame(maxWidth: .infinity)
}
}
.onDelete(perform: { (item) in
currentStudySet.studySet.studyItem.remove(atOffsets: item)
})
}
}
}
class HomeViewModel: ObservableObject {
#Published var studySet: StudyModel = StudyModel()
}
struct StudyModel: Hashable{
var title: String = ""
var days = ["One day", "Two days", "Three days", "Four days", "Five days", "Six days", "Seven days"]
var studyGoals = "One day"
var studyItem: [StudyItemModel] = [StudyItemModel(itemTitle: "test 1", itemDescription: "hello"), StudyItemModel(itemTitle: "test 2", itemDescription: "hello"), StudyItemModel(itemTitle: "test 3", itemDescription: "hello")]
}
struct StudyItemModel: Hashable, Identifiable{
let id = UUID()
var itemTitle: String = ""
var itemDescription: String = ""
}

Related

Swift 2.0 .contextMenu Multiple Delete From Core Data

First time post in here and new to coding... so I hope I am following proper protocol. I am putting together a view in Xcode 12.2 (SwiftUI 2) that outputs a list of data from Core Data and have a context menu to provide the user the option to edit, delete, and delete multiple. The context menu is working properly for edit and delete, however, I am facing a road block in how to implement the functionality to delete multiple list items. I am imagining the user would hard press one of the list items, the context menus pops open and if they press the "Delete Multiple" option, the view activates something similar to an edit mode that populates little circle on the left of each item which the user can select and delete more than one item at a time. I can see other article on how to do this, however, I cannot find guidance on how to implement this through Core Data. I have pasted my code below.
Please let me know if I am missing any other information that would make my question more clear.
I really appreciate the forums expertise and guidance.
Struct List : View {
#StateObject var appData = AppViewModel()
#Environment(\.managedObjectContext) var context
//Fetch Data...
#FetchRequest(entity: EntryData.entity(), sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], animation: .spring()) var results : FetchedResults<EntryData>
var body : some View {
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom), content: {
VStack{
ScrollView(.vertical, showsIndicators: false, content: {
LazyVStack(alignment: .leading, spacing: 20){
ForEach(results){task in
VStack(alignment: .leading, spacing: 5, content: {
Text(task.category ?? "")
.font(.title)
.fontWeight(.bold)
Text(task.date ?? Date(), style:. date)
.fontWeight(.bold)
Text("\(task.number.formattedCurrencyText)")
})
.padding(.horizontal, 14)
.padding(.top, 10)
.foregroundColor(Color("ColorTextList"))
.contextMenu{
Button(action: {appData.editItem(item: task)}, label: {
Text("Edit")
})
Button(action: {
context.delete(task)
try! context.save()
}, label: {
Text("Delete")
})
Button(action: {}, label: {
Text("Delete Mutiple")
})
}
}
}
})
}
VStack(){
VisualEffectView(effect: UIBlurEffect(style: .regular))
.frame(width: UIScreen.main.bounds.width, height: 50, alignment: .top)
.edgesIgnoringSafeArea(.all)
.background(Color.clear)
Spacer()
}
})
.background(Color.clear)
.sheet(isPresented: $appData.isNewData, content: {
AddDataView(appData: appData)
.environment(\.managedObjectContext, self.context)
})
}
}
Adding the viewModel of the app. How do I tap into into this and delete each of the attributes in a multi-list selection?
import SwiftUI
import CoreData
class AppViewModel : ObservableObject, Identifiable{
#Published var cateogry = ""
#Published var date = Date()
#Published var number : Double? = nil
#Published var notes = ""
#Published var id = UUID()
}
And adding the actual Core Data model screenshot.
You can implement a Selection Set for your List. This will contain all elements that are selected. Then you can dynamically show the contextMenu for delete or deleteAll based on the count of the set. Here is a full example with the implementation of deleteAll
struct SelectionDemo : View {
#State var demoData = ["Dave", "Tom", "Phillip", "Steve"]
#State var selected = Set<String>()
var body: some View {
HStack {
List(demoData, id: \.self, selection: $selected){ name in
Text(name)
.contextMenu {
Button(action: {
//Delete only one item
}){
Text("Delete")
}
if (selected.count > 1) {
Button(action: {
//Delete all
deleteAll()
})
{
Text("Delete all")
}
}
}
}.frame(width: 500, height: 460)
}
}
func deleteAll() {
for element in selected {
self.demoData.removeAll(where: {
$0 == element
})
}
}
}

How to pass string array data to navigationLink in SwiftUI

I'm using SwiftUI and having some problem passing my string array to a view.
Let's me explain the situation. I'm working on a Gallery app to show some artist's paintings.
I create a TabView to show all the paintings from each artist without any problem but I wanted to make each paintings clickable to see the detail view of the painting, and this where I get stuck.
Every time I click on a paintings it's show me the same paintings...
here is the samples code:
Model & View Model
let artistData: [Artist] = [
Artist(
name: "Piotre",
profilePic: "Piotre",
biography: "Piotre, est un...",
worksImages: [
"GO LOVE YOUR SELF 115.5-89",
"GRAFFITI THERAPIE 146-226",
"HELLO MY NAME IS 130-162",
"KING'S GARDEN 162-130",
"LION'S GARDEN 100-100",
"R'S GARDEN 162 130"
],
workName: [
"GO LOVE YOUR SELF",
"GRAFFITI THERAPIE",
"HELLO MY NAME IS",
"KING'S GARDEN",
"LION'S GARDEN",
"R'S GARDEN"
], workSize: [
"115.5-89",
"146-226",
"130-162",
"162-130",
"100-100",
"162 130"
]),
]
struct Artist: Identifiable {
var id = UUID()
var name: String
var profilePic: String
var biography: String
var worksImages: [String]
var workName: [String]
var workSize: [String]
}
struct ArtistGalleryView: View {
//MARK:- PROPERTIES
var work: Artist
//MARK:- BODY
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.6549019608, green: 0.7137254902, blue: 0.862745098, alpha: 1)).opacity(0.2)
.edgesIgnoringSafeArea(.all)
VStack {
//MARK:- Tableaux
TabView {
ForEach(work.worksImages, id: \.self) { works in
Image(works)
.resizable()
.scaledToFit()
}
.padding()
}
.tabViewStyle(PageTabViewStyle())
}
}
.frame(width: 330, height: 400, alignment: .center)
.cornerRadius(10)
}
}
struct GalleryView: View {
//MARK:- PROPERTIES
var artist: [Artist] = artistData
init() {
UINavigationBar.appearance().titleTextAttributes = [.font: UIFont(name: "WorkSans-Bold", size: 20)!]
}
//MARK:- BODY
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
VStack {
ForEach(artist) { item in
VStack(alignment: .leading) {
ArtistName(artist: item)
NavigationLink(destination: WorksDetailView(work: item)) {
ArtistGalleryView(work: item)
}
}
.buttonStyle(PlainButtonStyle())
}
.padding()
}
}
.navigationBarItems(trailing:Button(action: {
}) {
}
)
.navigationBarTitle(
Text("Gallery"), displayMode: .inline)
}
}
}
And my detail view where I want to see the corresponding image
struct WorksDetailView: View {
#State private var moveUp = false
var work: Artist
var body: some View {
ZStack {
VStack(alignment: .leading) {
Text(work.workName[0])
.modifier(CustomFontModifier(size: 22, name: "WorkSans-Bold"))
.padding(.horizontal)
Text(work.workSize[0])
.modifier(CustomFontModifier(size: 17, name: "WorkSans-Light"))
.foregroundColor(.secondary)
.padding(.horizontal)
VStack {
Image(work.worksImages[0])
.resizable()
.scaledToFit()
}
.padding()
}
.padding(.horizontal)
.padding(.bottom, 150)
Button(action: {
print("Show AR")
}) {
NeumorphicButton(moveUp: $moveUp)
}
.onAppear(perform: {
withAnimation(.easeInOut(duration: 1)) {
moveUp.toggle()
}
})
.offset(y: moveUp ? 225 : 450)
}
}
}

Swift UI | Textfield not reading entered value

I have a textfield which is supposed to log the units of a food product someone has eaten, which is then used to calculate the total number of calories, protein, etc. that the user consumed. But when the value is entered on the textfield, the units variable isn't updated. How can I fix this?
This is my code:
#State var selectFood = 0
#State var units = 0
#State var quantity = 1.0
#State var caloriesInput = 0.0
#State var proteinInput = 0.0
#State var carbsInput = 0.0
#State var fatsInput = 0.0
var body: some View {
VStack {
Group {
Picker(selection: $selectFood, label: Text("What did you eat?")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white))
{
ForEach(database.productList.indices, id: \.self) { i in
Text(database.productList[i].name)
}
}
.pickerStyle(MenuPickerStyle())
Spacer(minLength: 25)
Text("How much did you have?")
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.white)
.frame(alignment: .leading)
//Textfield not working.
TextField("Units", value: $units, formatter: NumberFormatter())
.padding(10)
.background(Color("Settings"))
.cornerRadius(10)
.foregroundColor(Color("Background"))
.keyboardType(.numberPad)
Button (action: {
self.quantity = ((database.productList[selectFood].weight) * Double(self.units)) / 100
caloriesInput = database.productList[selectFood].calories * quantity
proteinInput = database.productList[selectFood].protein * quantity
carbsInput = database.productList[selectFood].carbs * quantity
fatsInput = database.productList[selectFood].fats * quantity
UIApplication.shared.hideKeyboard()
}) {
ZStack {
Rectangle()
.frame(width: 90, height: 40, alignment: .center)
.background(Color(.black))
.opacity(0.20)
.cornerRadius(15)
;
Text("Enter")
.foregroundColor(.white)
.fontWeight(.bold)
}
}
}
}
}
This is an issue with NumberFormatter that has been going on for a while. If you remove the formatter it updates correctly.
This is a workaround. Sadly it requires 2 variables.
import SwiftUI
struct TFConnection: View {
#State var unitsD: Double = 0
#State var unitsS = ""
var body: some View {
VStack{
//value does not get extracted properly
TextField("units", text: Binding<String>(
get: { unitsS },
set: {
if let value = NumberFormatter().number(from: $0) {
print("valid value")
self.unitsD = value.doubleValue
}else{
unitsS = $0
//Remove the invalid character it is not flawless the user can move to in-between the string
unitsS.removeLast()
print(unitsS)
}
}))
Button("enter"){
print("enter action")
print(unitsD.description)
}
}
}
}
struct TFConnection_Previews: PreviewProvider {
static var previews: some View {
TFConnection()
}
}

SwiftUI: Generic parameter 'C0' could not be inferred

I've been working with this code and I keep getting this error: ''Generic parameter 'C0' could not be inferred'' Additionally it says 'In call to function 'buildBlock' (SwiftUI.ViewBuilder)'on my HStack when I include this line of code:
self.userData.tempBatchUnit = productName
I am not sure why. The code works fine without that line of code. Many thanks
struct enterProductUnitView: View {
#EnvironmentObject var userData: UserData
#State var productName: String = ""
var body: some View {
VStack {
HStack { // error Generic parameter 'C0' could not be inferred
Text("Product Unit:")
.font(.headline)
Spacer()
NavigationLink(destination: InstructionsView(desireInstructions: "Product Unit")) {
Text("?")
}
}
Text("ex: bags of popcorn, jars of jam etc.")
.font(.subheadline)
TextField("Enter here", text: $productName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.padding(.leading)
self.userData.tempBatchUnit = productName
}
}
}
Remove the following line - it is not allowed in bodyview builder
self.userData.tempBatchUnit = productName
I assume it should be in .onCommit
TextField("Enter here", text: $productName, onCommit: {
self.userData.tempBatchUnit = self.productName
})
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.padding(.leading)

SwiftUI Textfield Controls, Errors on Exit

SwiftUI Textfield Controls, Errors on Exit
I'm receiving multiple console chatter items when simply moving from textfield to
textfield in SwiftUI. When adding a new record or editing an existing one, if I
hit the keyboard return key (running on a device) I receive no warnings or errors. If
I simply tap another textfield (without using the return key) I get all this chatter.
Start:
19:45:46.969678-0700 ToDo[688:477250] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"",
"",
"",
""
)
Will attempt to recover by breaking constraint
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.
2019-08-29 19:45:46.995748-0700 ToDo[688:477250] [Snapshotting] Snapshotting a view (0x14fe87ff0, _UIReplicantView) that has not been rendered at least once requires afterScreenUpdates:YES.
End:
In both cases the data is saved correctly. I am storing the data in Core Data.iOS 13.1, Xcode 11M392r - website says Beta 7, my Xcode says this build is Beta 6, Catalina19A546d.
Here's the view:
struct AddNewToDo: View {
#Environment(\.managedObjectContext) var managedObjectContext
#Environment(\.presentationMode) var presentationMode
#State private var updatedTitle: String = "No Title"
#State private var updatedFirstName: String = "No Title"
#State private var updatedLastName: String = "No Title"
#State private var updatedDate: Date = Date()
#State private var updatedDateString: String = "July 2019"
var body: some View {
VStack {
VStack(alignment: .leading) {
Text("ToDo Title:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter a Title", text: $updatedTitle)
.onAppear {
self.updatedTitle = ""
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack(alignment: .leading) {
Text("First Name:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter First Name", text: $updatedFirstName)
.onAppear {
self.updatedFirstName = ""
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack(alignment: .leading) {
Text("Last Name:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter Last Name", text: $updatedLastName)
.onAppear {
self.updatedLastName = ""
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack(alignment: .leading) {
Text("Created Date:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter a date", text: $updatedDateString)
.onAppear {
let formatter = DateFormatter()
formatter.timeZone = .current
formatter.dateFormat = "M-d-yyyy HH:mm:ss"
let myString = formatter.string(from: Date())
//let myCoreDataString = formatter.string(from: self.toDoItem.createdAt!)
//print(myString)
//print("myCoredataString is \(myCoreDataString)")
self.updatedDateString = myString
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack {
Button(action: ({
let nmo = ToDoItem(context: self.managedObjectContext)
nmo.id = UUID()
nmo.title = self.updatedTitle
nmo.firstName = self.updatedFirstName
nmo.lastName = self.updatedLastName
nmo.createdAt = Date()
do {
try self.managedObjectContext.save()
} catch {
print(error)
}
self.updatedTitle = ""
self.updatedFirstName = ""
self.updatedLastName = ""
self.updatedDateString = ""
self.presentationMode.wrappedValue.dismiss()
})) {
Text("Save")
}
.padding()
}
.padding(10)
Spacer()
}
}
}
Any guidance would be appreciated.

Resources