SwiftUI - How to append data to nested struct? - arrays

I have Student class that is connected to a struct - Details. Which has a nested struct - Subjects. I know how to append to a normal struct or class but I am having difficulties trying to append to a nested struct. What I have is a Form where a student's name and number of subjects are asked after which they have to enter the subject name and grade. Then, press the save button in the Toolbar items/ NavigationBarItems.
class Students: ObservableObject {
#Published var details = [Details]()
}
struct Details: Identifiable {
let id = UUID()
let name: String
struct Subjects: Identifiable {
let id = UUID()
let name: String
let grade: String
}
let subjects: [Subjects]
}
The View class:
import SwiftUI
struct TestStudentView: View {
#StateObject var students = Students()
#State private var name = ""
#State private var numberOfSubjects = ""
#State private var subject = [String](repeating: "", count: 10)
#State private var grade = [String](repeating: "", count: 10)
#State private var details = [Details.Subjects]()
var body: some View {
NavigationView {
Group {
Form {
Section(header: Text("Student details")) {
TextField("Name", text: $name)
TextField("Number of subjects", text: $numberOfSubjects)
}
let count = Int(numberOfSubjects) ?? 0
Text("Count: \(count)")
Section(header: Text("Subject grades")) {
if count>0 && count<10 {
ForEach(0 ..< count) { number in
TextField("Subject", text: $subject[number])
TextField("Grade", text: $grade[number])
}
}
}
}
VStack {
ForEach(students.details) { student in
Text(student.name)
ForEach(student.subjects) { subject in
HStack {
Text("Subject: \(subject.name)")
Text("Grade: \(subject.grade)")
}
}
}
}
}
.navigationTitle("Student grades")
.navigationBarItems(trailing:
Button(action: {
//let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
//students.details.append(details)
//^Above to append
}, label: {
Text("Save")
})
)
}
}
}
I have tried creating a variable of type [Subjects] but that would not let me append to it after the Textfield values are entered it gives me the error : “Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols” (Which makes sense, as it would require a button). I have also tried appending to it once the save button is pressed using a ForEach but that also gives me the same error.

I think you want the model change to be the responsibility of your Students class. Try adding a public method to Students and call it from your view, like this:
class Students: ObservableObject {
#Published var details = [Details]()
public func addDetails(_ details : Details) {
details.append(details)
}
}
Then in your button action in the View, replace students.details.append(details) with a call to this method:
let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
students.details.append(details)
Is that what you're trying to do?

Your view tries to do too much for a single view. Your trying to add students and grades in a single view. The answer of Asperi is correct. However your error is indicating that something else is wrong with your code. Try to run the code below in an isolated environment and it should work fine.
For now I just added a save button for each grade it will add the grade to the first student always.
import SwiftUI
class Students: ObservableObject {
#Published var details = [Details]()
}
struct Details: Identifiable {
let id = UUID()
let name: String
struct Subjects: Identifiable {
let id = UUID()
let name: String
let grade: String
}
var subjects: [Subjects]
}
struct TestStudentView: View {
#StateObject var students = Students()
#State private var name = ""
#State private var numberOfSubjects = ""
#State private var subject = [String](repeating: "", count: 10)
#State private var grade = [String](repeating: "", count: 10)
#State private var details = [Details.Subjects]()
var body: some View {
NavigationView {
Group {
Form {
Section(header: Text("Student details")) {
TextField("Name", text: $name)
TextField("Number of subjects", text: $numberOfSubjects)
}
let count = Int(numberOfSubjects) ?? 0
Text("Count: \(count)")
Section(header: Text("Subject grades")) {
if count>0 && count<10 {
ForEach(0 ..< count) { number in
TextField("Subject", text: $subject[number])
TextField("Grade", text: $grade[number])
Button(action: {
if students.details.count > 0 {
var test = students.details[0]
students.details[0].subjects.append(Details.Subjects(name: subject[number], grade: grade[number]))
}
}) {
Text("Save")
}
}
}
}
}
VStack {
ForEach(students.details) { student in
Text(student.name)
ForEach(student.subjects) { subject in
HStack {
Text("Subject: \(subject.name)")
Text("Grade: \(subject.grade)")
}
}
}
}
}
.navigationTitle("Student grades")
.navigationBarItems(trailing:
Button(action: {
let details = Details(name: name, subjects: [Details.Subjects(name: "Physics", grade: "A"), Details.Subjects(name: "Computer Science", grade: "A*")])
students.details.append(details)
//^Above to append
}, label: {
Text("Save")
})
)
}
}
}

Related

How to append data to an array from another view (swiftUI)?

In the first view I have a textfield and a date picker. How do I append that information to the array in another view so that the inputed information is added to a list?
I have this struct:
import Foundation
struct Friend: Identifiable {
let name: String
let bday: String
let id = UUID()
}
and this list:
import SwiftUI
struct FriendView: View {
#State private var friends = [Friend]()
var body: some View {
List(friends) { Friend in
HStack {
Text(Friend.name)
Spacer()
Text(Friend.bday)
}
}
}
}
struct FriendView_Previews: PreviewProvider {
static var previews: some View {
FriendView()
}
}
and this is the view where I want to append the information from the form to the friends array but I keep getting "Value of type 'AddFriendView' has no member 'friends'" error at the addFriend function.
struct AddFriendView: View {
#State var input: String = ""
#State var date: Date = Date()
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}
func addFriend () {
self.friends.append(Friend(name: "name", bday: "birthday")) //ERROR POPS UP HERE
dump(friends)
}
var body: some View {
VStack {
Spacer()
TextField("Enter name", text: $input)
.foregroundColor(.yellow)
.padding(.all)
.multilineTextAlignment(.center)
.disableAutocorrection(true)
.font(.largeTitle)
.onTapGesture {
self.hideKeyboard()
}
Text("was born on a date")
.font(.title)
.foregroundColor(.yellow)
DatePicker(
"",
selection: $date,
displayedComponents: [.date]
)
.labelsHidden()
Spacer()
Button(
"Save birthday"
) {
self.addFriend()
//print("\(self.input) \t \(self.dateFormatter.string(from: self.date))")
self.input = ""
}
.padding(.all, 50)
.accentColor(.white)
.background(Color.yellow)
.cornerRadius(25)
// Spacer()
}
}
}
Any help would be greatly appreciated. Thank you in advance
I tried adding #Binding var friends but then I get "Type annotation missing in pattern" error and also #Binding var friends: [Friend] but then I get "Missing argument for parameter 'friends' in call" error at the end here:
struct AddFriendView_Previews: PreviewProvider {
static var previews: some View {
AddFriendView()
}
}
#Binding var friends: [Friend]
is correct. And the missing argument in the preview can be accomplished by providing a constant, an empty array
struct AddFriendView_Previews: PreviewProvider {
static var previews: some View {
AddFriendView(friends: .constant([]))
}
}
or a sample friend
struct AddFriendView_Previews: PreviewProvider {
static var previews: some View {
AddFriendView(friends: .constant([Friend(name: "John Doe", bday: "Jan 1, 2000")]))
}
}

Swift Struct in Array of Structs not updating to new values

This is my data structure
struct SPPWorkout: Codable {
static let setKey = "Sets"
static let exerciseID = "id"
var id: Double? = 0.0
var duration: String?
var calories: Int?
var date: String?
var exercises: [ExerciseSet]
[...]
}
struct ExerciseSet: Codable {
let id: String
let name: String
var reps: Int
var weight: Double
[...]
}
extension ExerciseSet: Equatable {
static func ==(lhs: ExerciseSet, rhs: ExerciseSet) -> Bool {
return lhs.id == rhs.id
}
}
and in a SwiftUI view I'm trying to modify an ExerciseSet from user input
#State private var sppWorkout: SPPWorkout!
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ?? 0.0
self.editingIndex = nil
})
}
The issue is here
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ??
and I've tried in all ways to update it, both from the view and with a func in SPPWorkout. I've also tried to replace the object at index
var newSet = ExerciseSet(id: [...], newValues)
self.exercises[editingIndex] = newSet
but in no way it wants to update. I'm sure that somewhere it creates a copy that it edits but I have no idea why and how to set the new values.
Edit: if I try to delete something, it's fine
sppWorkout.exercises.removeAll(where: { $0 == sppWorkout.exercises[index]})
Edit 2:
It passes the guard statement and it does not change the values in the array.
Edit 3:
At the suggestion below from Jared, I've copied the existing array into a new one, set the new values then tried to assign the new one over to the original one but still, it does not overwrite.
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
print(sppWorkout.exercises)
guard let editingIndex = editingIndex else { return }
var copyOfTheArray = sppWorkout.exercises
copyOfTheArray[editingIndex].reps = Int(reps) ?? 0
copyOfTheArray[editingIndex].weight = Double(weight) ?? 0.0
//Copy of the array is updated correctly, it has the new values
sppWorkout.exercises = copyOfTheArray
//Original array doesn't get overwritten. It still has old values
self.editingIndex = nil
Edit 4: I've managed to make progress by extracting the model into a view model and updating the values there. Now the values get updated in sppWorkout, but even though I call objectWillChange.send(), the UI Update doesn't trigger.
full code:
class WorkoutDetailsViewModel: ObservableObject {
var workoutID: String!
#Published var sppWorkout: SPPWorkout!
func setupData(with workoutID: String) {
sppWorkout = FileIOManager.readWorkout(with: workoutID)
}
func update(_ index: Int, newReps: Int, newWeight: Double) {
let oldOne = sppWorkout.exercises[index]
let update = ExerciseSet(id: oldOne.id, name: oldOne.name, reps: newReps, weight: newWeight)
sppWorkout.exercises[index] = update
self.objectWillChange.send()
}
}
struct WorkoutDetailsView: View {
var workoutID: String!
#StateObject private var viewModel = WorkoutDetailsViewModel()
var workout: HKWorkout
var dateFormatter: DateFormatter
#State private var offset = 0
#State private var isShowingOverlay = false
#State private var editingIndex: Int?
#EnvironmentObject var settingsManager: SettingsManager
#Environment(\.dismiss) private var dismiss
var body: some View {
if viewModel.sppWorkout != nil {
VStack {
ListWorkoutItem(workout: workout, dateFormatter: dateFormatter)
.padding([.leading, .trailing], 10.0)
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
let index = viewModel.sppWorkout.exercises.firstIndex(of: exercise) ?? 0
DetailListSetItem(exerciseSet: viewModel.sppWorkout.exercises[index], set: index + 1)
.environmentObject(settingsManager)
.swipeActions {
Button(role: .destructive, action: {
viewModel.sppWorkout.exercises.removeAll(where: { $0 == viewModel.sppWorkout.exercises[index]})
} ) {
Label("Delete", systemImage: "trash")
}
Button(role: .none, action: {
isShowingOverlay = true
editingIndex = index
} ) {
Label("Edit", systemImage: "pencil")
}.tint(.blue)
}
}
.padding([.leading, .trailing], -30)
//iOS 16 .scrollContentBackground(.hidden)
}
.overlay(alignment: .bottom, content: {
editOverlay
.animation(.easeInOut (duration: 0.5), value: isShowingOverlay)
})
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action : {
do {
try FileIOManager.write(viewModel.sppWorkout, toDocumentNamed: "\(viewModel.sppWorkout.id ?? 0).json")
} catch {
Debugger.log(error: error.localizedDescription)
}
dismiss()
}){
Image(systemName: "arrow.left")
})
} else {
Text("No workout details found")
.italic()
.fontWeight(.bold)
.font(.system(size: 35))
.onAppear(perform: {
viewModel.setupData(with: workoutID)
})
}
}
#ViewBuilder private var editOverlay: some View {
if isShowingOverlay {
ZStack {
Button {
isShowingOverlay = false
} label: {
Color.clear
}
.edgesIgnoringSafeArea(.all)
VStack{
Spacer()
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
print(viewModel.sppWorkout.exercises)
print("dupa aia:\n")
viewModel.update(editingIndex, newReps: Int(reps) ?? 0, newWeight: Double(weight) ?? 0.0)
print(viewModel.sppWorkout.exercises)
self.editingIndex = nil
})
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color("popupBackground"),
lineWidth: 3)
)
}
}
}
}
}
So I got a very good explanation on reddit on what causes the problem. Thank you u/neddy-seagoon if you are reading this.
The explanation
. I believe that updating an array will not trigger a state update. The only thing that will, with an array, is if the count changes. So
sppWorkout.exercises[index].reps = newReps
will not cause a trigger. This is not changing viewModel.sppWorkout.exercises.indices
So all I had to to was modify my List from
List(viewModel.sppWorkout.exercises, id: \.id)
to
List(viewModel.sppWorkout.exercises, id: \.hashValue)
as this triggers the list update because the hashValue does change when updating the properties of the entries in the list.
For the line
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
Replace with
List(viewModel.sppWorkout.exercises, id: \.self) { exercise in

Appending Data Read in from FireBase to Array results in Empty Array Swift

I have two frames, one which displays the employees and another that adds employees, which then updates the first frame using #Binding. In the ListFrame struct where I display the list of employees, I am attempting to read in the data from Firebase (getData func) and append it to the array. I am successful in reading the data, as the employeesTemp array which I initialize in the getData function gets populated correctly. However, when I try to append the values in the correctly populated employeesTemp array to the employees global array, the global array stays empty, which means my first frame doesn't display anything. I have read other posts that talk about the fact that Firebase's operations are async, but after going through dozens of stackoverflows I have not been able to find a solution. Any help is appreciated.
import SwiftUI
import Firebase
struct Employee: Identifiable {
let id = UUID()
var firstName: String
var lastName: String
var emailID: String
var education: String
// combo drop down box
var department: String
var address: String
var salary: String
}
struct ListFrame: View {
#State var employees = [Employee]()
init() {
self.getData()
}
func getData() {
// get reference to data base
let db = Firestore.firestore()
// temp employee list (gets populated correctly)
var employeesTemp = [Employee]()
// read in the employees from employeelist data base
db.collection("EmployeeList").getDocuments { snapshot, error in
// checking for errors
if error == nil {
// no errors
if let snapshot = snapshot {
// get all documents and create employees
DispatchQueue.main.async {
print("adding employee")
employeesTemp = [Employee]()
employeesTemp = snapshot.documents.map { document in
let new_employee:Employee = Employee(firstName: document["firstName"] as? String ?? "", lastName: document["lastName"] as? String ?? "" , emailID: document["emailID"] as? String ?? "", education: document["education"] as? String ?? "", department: document["department"] as? String ?? "", address: document["address"] as? String ?? "", salary: document["salary"] as? String ?? "")
print(new_employee)
employeesTemp.append(new_employee)
print(employeesTemp)
print("appending to global employees")
employees.append(contentsOf: employeesTemp)
print(employees)
//self.employees = employeesTemp
return new_employee
}
}
}
}
else {
// handle error
print("error")
}
}
}
var body: some View {
NavigationView {
VStack {
Spacer()
List {
ForEach(employees) { employee in
Text(employee.firstName)
}
}
.navigationTitle("List of Employees")
Spacer()
HStack {
Button(action: {
}) {
NavigationLink(destination: AddEmployeeFrame(employees: $employees)) {
Text("Add Employee")
}
}
}
}
}
}
}
struct AddEmployeeFrame: View {
#State private var firstName: String = ""
#State private var lastName: String = ""
#State private var emailID: String = ""
#State private var education: String = ""
// combo drop down box
#State private var department: String = ""
#State private var address: String = ""
#State private var salaryString: String = ""
#Binding var employees: [Employee]
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
func addData(firstName: String, lastName: String, emailID: String, education: String, department: String, address: String, salary: String) {
// get reference to database
let db = Firestore.firestore()
// add a document to collection
db.collection("EmployeeList").addDocument(data: ["firstName": firstName, "lastName": lastName, "emailID": emailID,
"education": education, "department": department, "address": address,
"salary": salary]) { error in
if error == nil {
// no error
//self.getData()
}
else {
// handle error
}
}
}
var body: some View {
NavigationView {
// text fields
VStack(spacing: 50) {
Group {
TextField("First Name", text: $firstName).multilineTextAlignment(.center)
TextField("Last Name", text: $lastName).multilineTextAlignment(.center)
TextField("Email ID", text: $emailID).multilineTextAlignment(.center)
TextField("Education", text: $education).multilineTextAlignment(.center)
}
.navigationTitle("Add Employee")
Menu("Select Department") {
Button("Other") {
department = "Other"
print(department)
}
Button("IT Department") {
department = "IT Department"
print(department)
}
Button("Storage Department") {
department = "Storage Department"
print(department)
}
Button("HR Department") {
department = "HR Department"
print(department)
}
Button("Testing Department") {
department = "Testing Department"
print(department)
}
}
TextField("Address", text: $address).multilineTextAlignment(.center)
TextField("Salary", text: $salaryString).multilineTextAlignment(.center)
// cancel and save buttons
HStack() {
Spacer()
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
})
Spacer()
Button(action: {
let new_employee = Employee(firstName: firstName, lastName: lastName, emailID: emailID, education: education, department: department, address: address, salary: salaryString)
// adding data to server
self.addData(firstName: new_employee.firstName, lastName: new_employee.lastName, emailID: new_employee.emailID, education: new_employee.education, department: new_employee.department, address: new_employee.address, salary: new_employee.salary)
employees.append(new_employee)
print(employees)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Save")
})
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListFrame()
}
}

How to unite two different structures into one array and sort it? Swift

I'm building a feed, where I want to unite two different structures and sort the feed by date, here's how I've tried. It shows mistakes in View. It writes either "compiler unable to type check in reasonable time", either something else that connects to the ForEach loop.
Maybe you have an idea what can be an issue? Or, if there's other way to build and sort the feed?
EDIT: The following error: "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
class FeedViewModel: ObservableObject {
#Published var feed: [FeedCommonContent] = []
// download(ed) articles
self.feed.append(Article(id: doc.document.documentID, title: title, pic: pic, time: time.dateValue(), user: user))
// download(ed) tutorials
self.feed.append(Tutorial(id: doc.document.documentID, title: title, steps: [steps], notes: notes, pic: pic, time: time.dateValue(), user: user))
// sort the feed
self.feed.sort { (p1, p2) -> Bool in
return p1.time > p2.time
}
}
protocol FeedCommonContent {
// Define anything in common between objects
var time: Date { get set }
var id: String { get set }
var feedIdentity: FeedIdentity.RawValue { get } // Article, or Tutorial; or - { get set }
}
enum FeedIdentity: String {
// case Article, Tutorial
case Article = "Article"
case Tutorial = "Tutorial"
}
struct Article: Identifiable, FeedCommonContent {
var feedIdentity = FeedIdentity.Article.rawValue
var id: String = UUID().uuidString
var title: String
var pic: String
var time: Date
var user: User
}
struct Tutorial: Identifiable, FeedCommonContent {
var feedIdentity = FeedIdentity.Tutorial.rawValue
var id: String = UUID().uuidString
var titleImage: String
var name: String
var user: User
var inventory: [String]
var steps: [String]
var notes: String
var time: Date
var warnings: String
}
struct FeedView: View {
#StateObject var feedData = FeedViewModel()
var body: some View {
ScrollView {
ForEach(feedData.feed, id: \.self.id) { item in
// also tried type checking:
// if item is Article // or, if let article = item as? Article
if item.feedIdentity == FeedIdentity.Article.rawValue {
NavigationLink(destination: ArticleView(article: item, articleData: feedData)) {
ArticleUnitView(article: item, feedData: feedData)
}
} else if item.feedIdentity == FeedIdentity.Tutorial.rawValue { // if item is Tutorial
NavigationLink(destination: TutorialView(tutorial: item, feedData: feedData)) {
TutorialUnitView(tutorial: item, feedData: feedData)
}
}
}
}
When I did an array only of one of data structures, all worked. (i.e. either var articles: [Article] = [], or var tutorials: [Tutorial] = [] etc..)
this is the (test) code I used to show how to "sort" your feed by date, and display it in a View:
EDIT, using "Joakim Danielson" enum suggestion:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
FeedView()
}
}
}
class FeedViewModel: ObservableObject {
#Published var feed: [FeedCommonContent] = []
// for testing
init() {
self.feed.append(Article(title: "article title", pic: "pic1", time: Date(), user: User()))
self.feed.append(Tutorial(titleImage: "title1", name: "tut1", user: User(), inventory: [], steps: [], notes: "note1", time: Date(), warnings: ""))
sortFeed()
}
func sortFeed() {
feed.sort { $0.time > $1.time }
}
}
protocol FeedCommonContent {
// Define anything in common between objects
var time: Date { get set }
var id: String { get set }
var feedIdentity: FeedIdentity { get } // Article, or Tutorial; or - { get set }
}
enum FeedIdentity: String {
// case Article, Tutorial
case Article
case Tutorial
}
struct User: Identifiable {
var id: String = UUID().uuidString
var name: String = "user"
}
struct Article: Identifiable, FeedCommonContent {
let feedIdentity = FeedIdentity.Article
var id: String = UUID().uuidString
var title: String
var pic: String
var time: Date
var user: User
}
struct Tutorial: Identifiable, FeedCommonContent {
let feedIdentity = FeedIdentity.Tutorial
var id: String = UUID().uuidString
var titleImage: String
var name: String
var user: User
var inventory: [String]
var steps: [String]
var notes: String
var time: Date
var warnings: String
}
struct FeedView: View {
#StateObject var feedData = FeedViewModel()
var body: some View {
ScrollView {
ForEach(feedData.feed, id: \.id) { item in
// switch item.feedIdentity {
// case FeedIdentity.Article:
// NavigationLink(destination: ArticleView(article: item, articleData: feedData)) {
// ArticleUnitView(article: item, feedData: feedData)
// }
// case FeedIdentity.Tutorial:
// NavigationLink(destination: TutorialView(tutorial: item, feedData: feedData)) {
// TutorialUnitView(tutorial: item, feedData: feedData)
// }
// }
// for testing
switch item.feedIdentity {
case FeedIdentity.Article:
NavigationLink(destination: Text(item.feedIdentity.rawValue + " " + item.id)) {
Text(item.feedIdentity.rawValue)
}
case FeedIdentity.Tutorial:
NavigationLink(destination: Text(item.feedIdentity.rawValue + " " + item.id)) {
Text(item.feedIdentity.rawValue)
}
}
}
}
}

Find if a different array has the same element and open an Alert

I know from the question it looks like something that has been already answered on this website before, but please read until the end, because I can't find the answer.
So, I have an Array that contains values of TagsModel:
import SwiftUI
import Combine
class DataManager : Equatable, Identifiable, ObservableObject {
static let shared = DataManager()
#Published var storageTags : [TagsModel] = []
typealias StorageTags = [TagsModel]
//The rest of the code
}
And the TagsModel:
import SwiftUI
import Combine
class TagsModel : Codable, Identifiable, Equatable, ObservableObject {
var id = UUID()
var tagName : String
var value : [ValueModel] = []
init(tagName: String) {
self.tagName = tagName
}
static func == (lhs: TagsModel, rhs: TagsModel) -> Bool {
return lhs.id.uuidString == rhs.id.uuidString
}
}
If you need it, the ValueModel is:
import SwiftUI
import Combine
class ValueModel : Codable, Identifiable, Equatable, ObservableObject, Comparable {
var id = UUID()
var name : String
var notes : String?
var expires : Date?
init(name: String, notes: String?, expires: Date?) {
self.name = name
self.notes = notes
self.expires = expires
}
}
Now what I would like to do is let the user add the elements of type ValueModel to the array Value of each TagsModel (which in English means I would like users to be able to add values inside their belonging tags). I can do all this, but I would like to add a check: if any other TagsModel contains the value that the user is trying to add, show an Alert (since every value can have only one tag). This Alert should be asking the user whether he/she wants to add that value to the selected tag and remove it from the other one, or cancel the action.
What I managed to do up to now is this:
import SwiftUI
struct SelectValuesForTagsView: View {
#ObservedObject var dm: DataManager
var tm: TagsModel
#Binding var showSheetSelectValuesForTagsView : Bool
#State var showAlertValueAlreadyInTag = false
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#GestureState private var dragOffset = CGSize.zero
#Environment(\.colorScheme) var colorScheme
var body: some View {
NavigationView {
Form {
ForEach(dm.storageValues) { valuesOfForEach in
HStack {
if tm.value.contains(where: { $0.id.uuidString == valuesOfForEach.id.uuidString
}) {
Image(systemName: "checkmark.circle.fill")
.frame(width: 22, height: 22)
.foregroundColor(.green)
} else {
Image(systemName: "circle")
.frame(width: 22, height: 22)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
Button(action: {
if !tm.value.contains(where: { $0.id.uuidString == valuesOfForEach.id.uuidString
}) {
tm.value.append(valuesOfForEach)
dm.save()
} else {
guard let indexValue = tm.value.firstIndex(where: { $0.id.uuidString == valuesOfForEach.id.uuidString
}) else { return }
tm.value.remove(at: indexValue)
dm.save()
}
}, label: {
if tm.value.contains(where: { $0.id.uuidString == valuesOfForEach.id.uuidString
}) {
Text(valuesOfForEach.name)
.foregroundColor(.blue)
} else {
Text(valuesOfForEach.name)
.foregroundColor(.yellow)
}
})
}
}
}
.navigationBarTitle(Text("Add Values"), displayMode: .automatic)
.navigationBarBackButtonHidden(true)
.navigationBarItems(trailing: saveButton)
}
}
var saveButton: some View {
Button(action: {
let tag : TagsModel
if dm.storageTags.contains(where: {$0.value == tm.value}) {
showAlertValueAlreadyInTag = true
//The problem is here, that the Alert ALWAYS shows up, even though there's only this Tag containing that value
}
else {
dm.save()
showSheetSelectValuesForTagsView = false
}
}, label: {
Text("Save")
.foregroundColor(.blue)
}).alert(isPresented: $showAlertValueAlreadyInTag, content: { alertValueAlreadyInTag })
}
var alertValueAlreadyInTag : Alert {
Alert(title: Text("Attention!"), message: Text("Every value can only be assigned to one tag. One or more values you selected are already into another tag. Would you like to substitute the belonging tag for these values?"), primaryButton: Alert.Button.default(Text("Yes, substitute"), action: {
dm.save()
showAlertValueAlreadyInTag = false
showSheetSelectValuesForTagsView = false
}), secondaryButton: Alert.Button.default(Text("Cancel"), action: {
showAlertValueAlreadyInTag = false
}))
}
}
How can I check if another tag inside the storageTags has the already the same value inside of its value Array?

Resources