How to save data in array - ios

import Foundation
import SwiftUI
struct Person: Identifiable, Codable{
var id = UUID()
let name: String
var isFavorite: Bool
}
class People: ObservableObject{
#Published var group = [Person]() {
didSet {
if let encoded = try? JSONEncoder().encode(peopleData){
UserDefaults.standard.set(encoded, forKey: "peopleKey")
}
}
}
init(){
if let savedItems = UserDefaults.standard.data(forKey: "peopleKey"),
let decodedItems = try? JSONDecoder().decode([Person].self, from: savedItems) {
group = decodedItems
} else {
group = peopleData
}
}
var peopleData: [Person] = [
Person(name: "Bob", isFavorite: false),
Person(name: "John", isFavorite: false),
Person(name: "Kayle", isFavorite: false),
Person(name: "Alise", isFavorite: false)
]
}
I am tryn to save a chance on array. But when I relaunch its not saved.
import SwiftUI
struct ContentView: View {
#StateObject var model = People()
var body: some View {
VStack(alignment: .leading, spacing: 10){
Text(model.group[0].name)
.opacity(model.group[0].isFavorite ? 1:0)
Button(model.group[0].isFavorite ? "Remove from favorite" : "add to favorites") {
model.group[0].isFavorite.toggle()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I can toggle the isFavorite bool with button. But when I relaunch its not saved.
in the which part of this code I made a mistake and why its not working I can't figure out.
I tried a lot of way for save data in array but all attempts was failed.

currently you are saving peopleData, where you should be saving group. So try using:
class People: ObservableObject{
#Published var group = [Person]() {
didSet {
if let encoded = try? JSONEncoder().encode(group){ // <-- here group
UserDefaults.standard.set(encoded, forKey: "peopleKey")
}
}
}
// ...

Related

Sorting items by date. And adding numbers from today

This is Model and View Model. I am using UserDefaults for saving data.
import Foundation
struct Item: Identifiable, Codable {
var id = UUID()
var name: String
var int: Int
var date = Date()
}
class ItemViewModel: ObservableObject {
#Published var ItemList = [Item] ()
init() {
load()
}
func load() {
guard let data = UserDefaults.standard.data(forKey: "ItemList"),
let savedItems = try? JSONDecoder().decode([Item].self, from: data) else { ItemList = []; return }
ItemList = savedItems
}
func save() {
do {
let data = try JSONEncoder().encode(ItemList)
UserDefaults.standard.set(data, forKey: "ItemList")
} catch {
print(error)
}
}
}
and this is the view. I am tryng too add new item and sort them by date. After that adding numbers on totalNumber. I tried .sorted() in ForEach but its not work for sort by date. and I try to create a func for adding numbers and that func is not work thoo.
import SwiftUI
struct ContentView: View {
#State private var name = ""
#State private var int = 0
#AppStorage("TOTAL_NUMBER") var totalNumber = 0
#StateObject var VM = ItemViewModel()
var body: some View {
VStack(spacing: 30) {
VStack(alignment: .leading) {
HStack {
Text("Name:")
TextField("Type Here...", text: $name)
}
HStack {
Text("Number:")
TextField("Type Here...", value: $int, formatter: NumberFormatter())
}
Button {
addItem()
VM.save()
name = ""
int = 0
} label: {
Text ("ADD PERSON")
}
}
.padding()
VStack(alignment: .leading) {
List(VM.ItemList) { Item in
Text(Item.name)
Text("\(Item.int)")
Text("\(Item.date, format: .dateTime.day().month().year())")
}
Text("\(totalNumber)")
.padding()
}
}
}
func addItem() {
VM.ItemList.append(Item(name: name, int: int))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
First of all please name variables always with starting lowercase letter for example
#Published var itemList = [Item] ()
#StateObject var vm = ItemViewModel()
To sort the items by date in the view model replace
itemList = savedItems
with
itemList = savedItems.sorted{ $0.date < $1.date }
To show the sum of all int properties of the today items add a #Published var totalNumber in the view model and a method to calculate the value. Call this method in load and save
class ItemViewModel: ObservableObject {
#Published var itemList = [Item] ()
#Published var totalNumber = 0
init() {
load()
}
func load() {
guard let data = UserDefaults.standard.data(forKey: "ItemList"),
let savedItems = try? JSONDecoder().decode([Item].self, from: data) else { itemList = []; return }
itemList = savedItems.sorted{ $0.date < $1.date }
calculateTotalNumber()
}
func save() {
do {
let data = try JSONEncoder().encode(itemList)
UserDefaults.standard.set(data, forKey: "ItemList")
calculateTotalNumber()
} catch {
print(error)
}
}
func calculateTotalNumber() {
let todayItems = itemList.filter{ Calendar.current.isDateInToday($0.date) }
totalNumber = todayItems.map(\.int).reduce(0, +)
}
}
In the view delete the #AppStorage line because the value is calculated on demand and replace
Text("\(totalNumber)")
with
Text("\(vm.totalNumber)")

Update Details on a List SwiftUI

I'm fairly new to SwiftUI and I'm trying to update the details on a list and then save it.
I am able to get the details to update, but every time I try saving I'm not able to do it.
I have marked the area where I need help. Thanks in advance.
// This is the Array to store the items:
struct ExpenseItem : Identifiable, Codable {
var id = UUID()
let name: String
let amount: Int
}
// This is the UserDefault Array
class Expenses: ObservableObject {
#Published var items = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Items") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
items = decodedItems
return
}
}
items = []
}
}
// View to add details :
struct AddView: View {
#State private var name = ""
#State private var amount = 0
#StateObject var expenses: Expenses
#Environment(\.dismiss) var dismiss
var body: some View {
Form {
TextField("Name", text: $name)
Text("\(amount)")
Button("Tap Me") {
amount += 1
}
}
.navigationTitle("Add New Count")
.toolbar {
if name != "" {
Button("Save") {
let item = ExpenseItem(name: name, amount: amount)
expenses.items.append(item)
dismiss()
}
}
}
}
}
// This is the file to update the details:
struct UpdateDhikr: View {
#EnvironmentObject var expenses : Expenses
#State var name : String
#State var amount : Int
var body: some View {
Form {
TextField("Name", text: $name)
Text("\(amount)")
Button("Tap Me") {
amount += 1
}
}
.navigationTitle("Update Count")
.toolbar {
if name != "" {
Button("Save") {
// This is where I'm having problems.
}
}
}
}
}

Changing a boolean in a variable from Identifiable struct

import Foundation
import SwiftUI
struct Item: Identifiable, Codable{
var id = UUID()
var image: String
var name: String
var price: Int
var isFavorite: Bool
}
class Model: ObservableObject{
#Published var group = [Item]() {
didSet {
if let encoded = try? JSONEncoder().encode(group){
UserDefaults.standard.set(encoded, forKey: "peopleKey")
}
}
}
init(){
if let savedItems = UserDefaults.standard.data(forKey: "peopleKey"),
let decodedItems = try? JSONDecoder().decode([Item].self, from: savedItems) {
group = decodedItems
} else {
group = itemData
}
}
var itemData: [Item] = [
Item(image: "imageGIFT", name: "Flower",price: 5 , isFavorite: false),
Item(image: "imageGIFT", name: "Coffe Cup",price: 9 , isFavorite: false),
Item(image: "imageGIFT", name: "Teddy Bear",price: 2 , isFavorite: false),
Item(image: "imageGIFT", name: "Parfume",price: 8 , isFavorite: false)
]
}
I am trying to change variables on this struct and I define as var but after encode and decode they has been let. I changed let part to var then Xcode gived an error.
here is my test code that shows doing model.people[0].myPerson.toggle() in the Button
does work. I have made some minor mods and added some comments to the code.
I suggest again, read the very basics of Swift,
in particular the array section at: https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html.
Without understanding these very basic concepts you will keep struggling to code your App.
Note, there is probably no need for the myPeople array in your Model, but if that's what you want to have.
struct Person: Identifiable{
let id = UUID()
var name: String
var age: Int
var job: String
var myPerson: Bool
}
class Model: ObservableObject {
#Published var people: [Person] = []
#Published var myPeople: [Person] = []
init(){
addPeople()
}
// completely useless
func addPeople(){
people = peopleData
myPeople = peopleData.filter { $0.myPerson }
}
// here inside the class and using `Person` not `person`
var peopleData = [
Person(name: "Bob", age: 22, job: "Student", myPerson: false),
Person(name: "John", age: 26, job: "Chef", myPerson: false)
]
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
VStack {
VStack {
// this `myPeople` array is empty at first, nothing is displayed
ForEach(model.myPeople) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.blue)
Text("Age: \(person.age)").foregroundColor(.blue)
Text("Job: \(person.job)").foregroundColor(.blue)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.blue)
}.padding()
}
}
VStack {
// this `people` array has two items in it
ForEach(model.people) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.red)
Text("Age: \(person.age)").foregroundColor(.red)
Text("Job: \(person.job)").foregroundColor(.red)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.red)
}.padding()
}
}
Button("Click") {
print("\n--> before name: \(model.people[0].name) ")
print("--> before myPerson: \(model.people[0].myPerson) ")
model.people[0].name = "Franz"
model.people[0].myPerson.toggle()
print("\n--> after name: \(model.people[0].name) ")
print("--> after myPerson: \(model.people[0].myPerson) ")
// update the myPeople array (the blue items)
model.myPeople = model.people.filter { $0.myPerson }
}
}
}
}
Alternatively, you could use this code using only one array of people: [Person]:
class Model: ObservableObject {
#Published var people: [Person] = []
init(){
addPeople()
}
func addPeople() {
people = peopleData
}
// here inside the class and using `Person` not `person`
var peopleData = [
Person(name: "Bob", age: 22, job: "Student", myPerson: false),
Person(name: "John", age: 26, job: "Chef", myPerson: false)
]
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
VStack {
VStack {
// here filter on myPerson=true
ForEach(model.people.filter { $0.myPerson }) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.blue)
Text("Age: \(person.age)").foregroundColor(.blue)
Text("Job: \(person.job)").foregroundColor(.blue)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.blue)
}.padding()
}
}
VStack {
// here filter on myPerson=false
ForEach(model.people.filter { !$0.myPerson }) { person in
VStack(alignment: .leading){
Text("Name: \(person.name)").foregroundColor(.red)
Text("Age: \(person.age)").foregroundColor(.red)
Text("Job: \(person.job)").foregroundColor(.red)
Text("myPerson: " + String(person.myPerson)).foregroundColor(.red)
}.padding()
}
}
Button("Click") {
model.people[0].name = "Franz"
model.people[0].myPerson.toggle()
}
}
}
}

ObservableObject not updating view in nested loop SWIFTUI

Regarding the following project :
You have an amountSum of 100
When you click on one user "plus" button, this specific user have to pay this amount but if you click on multiple user "plus" button, the amount to pay is divided between them equally.
Any idea how I can update the entire Model2.MustPayM2 prop when I click on the "plus" button please ?
import SwiftUI
struct Model1: Identifiable, Codable {
var id: String = UUID().uuidString
var nameM1: String
var amountM1: Double
var amountSumM1: Double = 100
var arrayM2: [Model2]
var isVisible: Bool = false
}
struct Model2: Identifiable, Codable {
var id: String = UUID().uuidString
var nameM2: String
var amountM2: Double = 0
var mustPayM2: Bool = false
}
class ViewModel1: ObservableObject {
#Published var Publi1: Model1
#Published var Publi1s: [Model1] = []
#Published var Publi2: Model2
#Published var Publi2s: [Model2] = []
init() {
let pub2 = Model2(nameM2: "init")
let pub1 = Model1(nameM1: "init", amountM1: 0, arrayM2: [pub2])
self.Publi2 = pub2
self.Publi1 = pub1
var newPub1s: [Model1] = []
for i in (0..<5) {
let newNameM1 = "name\(i+1)"
let newAmountM1 = Double(i+1)
var newModel1 = Model1(nameM1: newNameM1, amountM1: newAmountM1, arrayM2: [pub2])
var newPub2s: [Model2] = []
for i in (0..<5) {
let newNameM2 = "\(newNameM1)-user\(i+1)"
let newModel2 = Model2(nameM2: newNameM2)
newPub2s.append(newModel2)
}
newModel1.arrayM2 = newPub2s
newPub1s.append(newModel1)
}
Publi1s = newPub1s
Publi1 = newPub1s[0]
Publi2s = newPub1s[0].arrayM2
Publi2 = newPub1s[0].arrayM2[0]
}
}
struct View1: View {
#EnvironmentObject var VM1: ViewModel1
#State private var tt: String = ""
private let screenHeight = UIScreen.main.bounds.height
var body: some View {
ZStack {
VStack {
ForEach(0..<VM1.Publi2s.count, id: \.self) { i in
Text("\(VM1.Publi2s[i].nameM2)")
Text(tt)
Button {
VM1.Publi2s[i].mustPayM2.toggle()
var a = VM1.Publi2s.filter { $0.mustPayM2 == true }
let b = VM1.Publi1.amountM1 / Double(a.count)
// How can I update the new props between all users ??
// for j in 0..<a.count {
// a[j].amountM2 = b
// }
} label: {
Image(systemName: "plus")
}
}
Spacer()
Button {
VM1.Publi1.isVisible.toggle()
} label: {
Text("SHOW ME")
}
Spacer()
}
View2()
.offset(y: VM1.Publi1.isVisible ? 0 : screenHeight)
}
}
}
struct View2: View {
#EnvironmentObject var VM1: ViewModel1
var body: some View {
VStack {
Spacer()
ForEach(0..<VM1.Publi2s.count, id: \.self) { i in
Text("\(VM1.Publi2s[i].amountM2)")
}
}
}
}
struct View2_Previews: PreviewProvider {
static var previews: some View {
Group {
View1()
}
.environmentObject(ViewModel1())
}
}
You implementation seems overly complicated and error prone. I´ve practically rewritten the code for this. I´ve added comments to make it clear what and why I have done certain things. If you don´t understand why, don´t hesitate to ask a question. But please read and try to understand the code first.
//Create one Model containing the individuals
struct Person: Identifiable, Codable{
var id = UUID()
var name: String
var amountToPay: Double = 0.0
var shouldPay: Bool = false
}
//Create one Viewmodel
class Viewmodel:ObservableObject{
//Entities being observed by the View
#Published var persons: [Person] = []
init(){
//Create data
persons = (0...4).map { index in
Person(name: "name \(index)")
}
}
//Function that can be called by the View to toggle the state
func togglePersonPay(with id: UUID){
let index = persons.firstIndex { $0.id == id}
guard let index = index else {
return
}
//Assign new value. This will trigger the UI to update
persons[index].shouldPay.toggle()
}
//Function to calculate the individual amount that should be paid and assign it
func calculatePayment(for amount: Double){
//Get all persons wich should pay
let personsToPay = persons.filter { $0.shouldPay }
//Calcualte the individual amount
let individualAmount = amount / Double(personsToPay.count)
//and assign it. This implementation will trigger the UI only once to update
persons = persons.map { person in
var person = person
person.amountToPay = person.shouldPay ? individualAmount : 0
return person
}
}
}
struct PersonView: View{
//pull the viewmodel from the environment
#EnvironmentObject private var viewmodel: Viewmodel
//The Entity that holds the individual data
var person: Person
var body: some View{
VStack{
HStack{
Text(person.name)
Text("\(person.amountToPay, specifier: "%.2f")$")
}
Button{
//toggle the state
viewmodel.togglePersonPay(with: person.id)
} label: {
//Assign label depending on person state
Image(systemName: "\(person.shouldPay ? "minus" : "plus")")
}
}
}
}
struct ContentView: View{
//Create and observe the viewmodel
#StateObject private var viewmodel = Viewmodel()
var body: some View{
VStack{
//Create loop to display person.
//Dont´t itterate over the indices this is bad practice
// itterate over the items themselves
ForEach(viewmodel.persons){ person in
PersonView(person: person )
.environmentObject(viewmodel)
.padding(10)
}
Button{
//call the func to calculate the result
viewmodel.calculatePayment(for: 100)
}label: {
Text("SHOW ME")
}
}
}
}

Problems saving data to UserDefaults

I'm struggling with saving some date to UserDefaults. I have a struct, an array of which I'm going to save:
struct Habit: Identifiable, Codable {
var id = UUID()
var name: String
var comments: String
}
Then, in the view, I have a button to save new habit to an array of habits and put it into UserDefaults:
struct AddView: View {
#State private var newHabit = Habit(name: "", comments: "")
#State private var name: String = ""
let userData = defaults.object(forKey: "userData") as? [Habit] ?? [Habit]()
#State private var allHabits = [Habit]()
var body: some View {
NavigationView {
Form {
Section(header: Text("Habit name")) {
TextField("Jogging", text: $newHabit.name)
}
Section(header: Text("Description")) {
TextField("Brief comments", text: $newHabit.comments)
}
}
.navigationBarTitle("New habit")
.navigationBarItems(trailing: Button(action: {
allHabits = userData
allHabits.append(newHabit)
defaults.set(allHabits, forKey: "userData")
}) {
addButton
})
}
}
}
When I tap the button, my app crashes with this thread: Thread 1: "Attempt to insert non-property list object (\n \"HabitRabbit.Habit(id: 574CA523-866E-47C3-B56B-D0F85EBD9CB1, name: \\\"Wfs\\\", comments: \\\"Sdfdfsd\\\")\"\n) for key userData"
What did I do wrong?
Adopting Codable doesn't make the object property list compliant per se, you have to encode and decode the object to and from Data.
Something like this
func loadData() -> [Habit]
guard let userData = defaults.data(forKey: "userData") else { return [] }
return try? JSONDecoder().decode([Habit].self, from: userData) ?? []
}
func saveData(habits : [Habit]) {
guard let data = try? JSONEncoder().encode(habits) else { return }
defaults.set(data, forKey: "userData")
}

Resources