Hi everyone. I'm just a beginner to Swift and I have encountered a problem with an app that I am building. The app can save School Subjects. In addsubjectview there is an option to select which terms/semesters the subject is part of. But after adding a new subject and then one more, the first subject added loses its terms/semesters selection. Please could someone assist me.
// SwiftUIView.swift
// MySchoolPlan
//
// Created by Vader on 12/19/24.
//
import SwiftUI
import EventKit
import EventKitUI
import SwiftData
@available(iOS 17.0, *)
struct AddSubjectView: View {
let daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
@State var selection: Int = 0
@State private var startTime: Date = setTimeToMidnight(for: Date())
@State private var endTime: Date = setTimeToMidnight(for: Date())
@Environment(\.modelContext) private var context
@State private var subject: String = ""
@State private var classroom: String = ""
@State private var teacherName: String = ""
@State private var itemName: String = ""
@State private var items: [Item] = []
@State private var day: [Time] = []
@Environment(\.dismiss) private var dismiss
@Query private var terms: [Term]
let store = EKEventStore()
@State private var showDaySelector: Bool = false
@State private var colour: Color = Color.accentColor
@State private var eOrS: Int = 1
@State private var tests: [Test] = []
@State private var testSheetPresented: Bool = false
@State private var testName: String = ""
@State private var testDate: Date = setTimeToMidnight(for: Date())
@State private var testSkills: [String] = []
@State private var testSkill: String = ""
@State private var selectedTest: Test?
@State private var termsForSubject: Set<UUID> = []
@State private var termSelectSheet: Bool = false
@State private var selectedItem: Item?
var body: some View {
NavigationStack {
VStack (spacing: 0) {
HStack {
if eOrS == 1 {
TextField("Subject", text: $subject, prompt: Text("Subject name").font(.largeTitle).fontWeight(.bold).foregroundColor(Color.gray)).textFieldStyle(.plain)
.padding().font(.largeTitle).bold()
} else {
TextField("Event", text: $subject, prompt: Text("Event name").font(.largeTitle).fontWeight(.bold).foregroundColor(Color.gray)).textFieldStyle(.plain)
.padding().font(.largeTitle).bold()
}
Spacer()
Button("Add", action: {
var selectedTerms: [Term] {
terms.filter {termsForSubject.contains($0.id) }
}
print(selectedTerms)
let newSubject = Subject(subjectName: subject, classroom: classroom, teacherName: teacherName, items: items, day: day, colour: colour.toHex() ?? "FFFFFF", eOrS: eOrS, tests: tests, terms: selectedTerms)
let fetchDescriptor = FetchDescriptor<Term>()
do {
let terms = try context.fetch(fetchDescriptor)
EventHelper().addEvent(newSubject)
print("Fetched \(terms.count) entities")
} catch {
print("Failed to fetch entities: \(error.localizedDescription)")
}
context.insert(newSubject)
try? context.save()
print(selectedTerms)
dismiss()
}).bold().disabled(subject.isEmpty || day.isEmpty || termsForSubject.isEmpty).padding(.trailing)
}
List(terms, id: \.id, selection: $termsForSubject) {term in
Text(term.name)
}.environment(\.editMode, .constant(.active)).presentationDetents([.fraction(0.3)])
}
}
}
}
#Preview {
if #available(iOS 17.0, *) {
AddSubjectView()
}
}
here are the class definitions:
import Foundation
import SwiftData
import SwiftUI
import UIKit
@available(iOS 17, *)
@available(iOS 17, *)
@Model
class Item: Identifiable {
var name: String = ""
init(name: String) {
self.name = name
}
}
@available(iOS 17, *)
@Model
class Subject: Identifiable {
var id = UUID()
var title: String
var classroom: String
var teacherName: String
var items: [Item]?
var day: [Time]
var colour: String
var eOrS: Int = 1
var tests: [Test] = []
var terms: [Term]
init(subjectName: String, classroom: String, teacherName: String, items: [Item]?, day: [Time], colour: String, eOrS: Int, tests: [Test], terms: [Term]) {
self.title = subjectName
self.classroom = classroom
self.teacherName = teacherName
self.items = items
self.day = day
self.colour = colour
self.eOrS = eOrS
self.tests = tests
self.terms = terms
}
}
@available(iOS 17, *)
@Model
class Time: Identifiable {
var dayName: String
var startTime: Date
var endTime: Date
@Attribute(.externalStorage) var identifier: [String] = []
init(dayName: String, startTime: Date, endTime: Date) {
self.dayName = dayName
self.startTime = startTime
self.endTime = endTime
}
}
@available(iOS 17, *)
@Model
class Term: Identifiable {
var id = UUID()
var name: String
var startDate: Date
var endDate: Date
init(name: String, startDate: Date, endDate: Date) {
self.name = name
self.startDate = startDate
self.endDate = endDate
}
}
@available(iOS 17, *)
@Model
class Test: Identifiable {
var name: String
var date: Date
@Attribute(.externalStorage) var skills: [String]
init(name: String, date: Date, skills: [String]) {
self.name = name
self.date = date
self.skills = skills
}
}
// Code below taken from Marco Eidinger's blog post on converting colours to strings and back: https://blog.eidinger.info/from-hex-to-color-and-back-in-swiftui
// Reason for use: I needed a way to store redundant colours in SwiftData and SwiftData is unable to store Color data types. This code converts a Color into a hex string for storing and then converts it back again.
extension Color {
init?(hex: String) {
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
var rgb: UInt64 = 0
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
var a: CGFloat = 1.0
let length = hexSanitized.count
guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
if length == 6 {
r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
b = CGFloat(rgb & 0x0000FF) / 255.0
} else if length == 8 {
r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
a = CGFloat(rgb & 0x000000FF) / 255.0
} else {
return nil
}
self.init(red: r, green: g, blue: b, opacity: a)
}
}
extension Color {
func toHex() -> String? {
let uic = UIColor(self)
guard let components = uic.cgColor.components, components.count >= 3 else {
return nil
}
let r = Float(components[0])
let g = Float(components[1])
let b = Float(components[2])
var a = Float(1.0)
if components.count >= 4 {
a = Float(components[3])
}
if a != Float(1.0) {
return String(format: "%02lX%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255), lroundf(a * 255))
} else {
return String(format: "%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255))
}
}
}