SwiftUI ListView doesn't refresh when needed

SwiftUI ListView doesn't refresh when needed
typescript
Ethan Jackson

Adding Modal it's a modal used to add person to people or change person's data. After changing if person's data ListView doesn't refresh.

import SwiftUI import iPhoneNumberField import PhotosUI struct InfoModal: View{ @Environment(PeopleStore.self) private var peopleStore @Environment(\.dismiss) var dismiss @State var isPresented: Bool = false @State var person: Person @State var isEditPressed: Bool = false var body: some View{ NavigationStack{ List{ if let _ = person.photo{ ZStack{ HStack{ Spacer() Image(uiImage: person.photo!) .resizable() .scaledToFill() .frame(maxHeight: 250) Spacer() } Button(action: { isPresented = true }, label:{ Text("Изменить") .background(.gray) .clipShape(RoundedRectangle(cornerRadius: 10)) .foregroundColor(.white) .font(.headline) .padding() .padding(.horizontal, 20) }) .offset(x: 150, y: -100) } }else{ ZStack{ HStack{ Spacer() Image(systemName: "person.circle.fill") .resizable() .frame(width: 100, height: 100) Spacer() } Button(action: { isPresented = true }, label:{ Text("Изменить") .background(.gray) .clipShape(RoundedRectangle(cornerRadius: 20)) .foregroundColor(.white) .offset(x: 0, y: 0) .font(.headline) .padding() .padding(.horizontal, 20) }) .offset(x: 150, y: -40) } } if person.name != ""{ VStack(alignment: .leading){ Text("Имя:") .foregroundColor(.gray) Text(person.name) } } if person.surname != ""{ VStack(alignment: .leading){ Text("Фамилия") .foregroundColor(.gray) Text(person.surname) } } if person.role != ""{ VStack(alignment: .leading){ Text("Статус:") .foregroundColor(.gray) Text(person.role) } } if person.birthday.date != ""{ VStack(alignment: .leading){ Text("Дата рождения:") .foregroundColor(.gray) HStack{ Text(person.birthday.date) .offset(x: 0) Text(".") .offset(x: -7) Text(person.birthday.month) .offset(x: -14) Text(".") .offset(x: -21) Text(person.birthday.year) .offset(x: -28) .offset(x: -5) Text(person.birthday.month) .offset(x: -10) Text(".") .offset(x: -15) Text(person.birthday.year) .offset(x: -20) } } } if person.phoneNumber != ""{ VStack(alignment: .leading){ Text("Телефон:") .foregroundColor(.gray) HStack{ Text(person.phoneNumber) } } } if person.email != ""{ VStack(alignment: .leading){ Text("Email:") .foregroundColor(.gray) Text(person.email) } } if person.address != ""{ VStack(alignment: .leading){ Text("Адрес:") .foregroundColor(.gray) Text(person.address) } } Button(action: { peopleStore.people = peopleStore.people.filter { $0.id != person.id } dismiss() }, label: { Text("Удалить") .font(.headline) .foregroundColor(.red) .offset(x: 150) }) } .listStyle(.grouped) } .sheet(isPresented: $isPresented){ Adding_Modal(person: &person) } } } struct Adding_Modal: View { @Environment(PeopleStore.self) private var peopleStore @Environment(\.dismiss) var dismiss @State var person: Person @State var isAdding: Bool @State var name: String @State var surname: String @State var phoneNumber: String @State var selectedItem: PhotosPickerItem? @State var selectedPhotoData: Data? @State var date: String @State var month: String @State var year: String @State var email: String @State var address: String @State var showsAlert = false @State var role: String init(person: inout Person) { self.person = person self.isAdding = person == Person() self.name = person.name self.surname = person.surname self.phoneNumber = person.phoneNumber self.selectedPhotoData = person.photo?.pngData() self.date = person.birthday.date self.month = person.birthday.month self.year = person.birthday.year self.email = person.email self.address = person.address self.role = person.role } var body: some View { NavigationStack{ List{ VStack{ HStack{ Spacer() if let selectedPhotoData, let image = UIImage(data: selectedPhotoData) { Image(uiImage: image) .resizable() .scaledToFill() .clipped() .clipShape(Circle()) .frame(width: 150, height: 150) }else{ Image(systemName: "person.circle.fill") .resizable() .frame(width: 150, height: 150) .foregroundColor(.black) .background(Color.white.opacity(0.5)) .clipShape(Circle()) } Spacer() } PhotosPicker(selection: $selectedItem, matching: .images) { Label("Выбрать фото", systemImage: "photo") } .onChange(of: selectedItem) { newItem in Task { if let data = try? await newItem?.loadTransferable(type: Data.self) { selectedPhotoData = data } } } } TextField("Имя", text: $name) TextField("Фамилия", text: $surname) TextField("Статус", text: $role) iPhoneNumberField("+7(___)___-__-__", text: $phoneNumber) HStack{ TextField(" DD", text: $date) .keyboardType(.numberPad) .background(Color.gray.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 5)) .frame(width: 35) .offset(x: 5) TextField(" MM", text: $month) .keyboardType(.numberPad) .background(Color.gray.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 5)) .frame(width: 35) TextField(" YYYY", text: $year) .keyboardType(.numberPad) .background(Color.gray.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 5)) .frame(width: 50) .offset(x: -5) } TextField("Email", text: $email) TextField("Адрес", text: $address) } .listStyle(.plain) .toolbar { ToolbarItem(placement: .navigationBarLeading){ Button(action: { dismiss() }){ Text("Отменить") .foregroundStyle(.blue) } } ToolbarItem(placement: .navigationBarTrailing) { Button(action: { if (name != "" || surname != "") && ((date>="01" && date<="31" && month>="01" && month<="12" && year >= "1900" && year <= "2025") || (date == "" && month == "" && year == "")){ person.role = role person.name = name person.surname = surname person.phoneNumber = phoneNumber person.email = email person.address = address person.birthday.date = date person.birthday.month = month person.birthday.year = year if let _ = selectedPhotoData{ person.photo = UIImage(data: selectedPhotoData!) }else{ person.photo = nil } if !isAdding{ peopleStore.people = peopleStore.people.filter { $0.id != person.id } } peopleStore.people.append(person) dismiss() }else{ showsAlert = true } }){ Text("Сохранить") .foregroundStyle(.blue) } .alert(isPresented: self.$showsAlert) { Alert(title: Text("Неверно заполнены поля")) } } } } } } struct ContentView: View { @Environment(PeopleStore.self) private var peopleStore @State var isPlusTapped: Bool = false @State var blankPers = Person() var body: some View { NavigationView{ List{ if !peopleStore.people.isEmpty{ ForEach(peopleStore.people){ person in PersonListView(person: person) } .onDelete(perform: { peopleStore.people.remove(atOffsets: $0) }) .onDelete{ delPers in peopleStore.people.remove(atOffsets: delPers) } }else{ Text("Список пуст.") .multilineTextAlignment(.center) .padding() .foregroundColor(.secondary) .font(.system(size: 25)) .frame(width: 500, height: 650) } } .navigationBarTitle("Пользователи") .listStyle(.plain) .offset(y: 0) .toolbar{ ToolbarItem(placement:.navigationBarTrailing){ Button(action: { isPlusTapped = true }){ Image(systemName: "plus") .foregroundColor(.blue) } } } .sheet(isPresented: $isPlusTapped){ Adding_Modal(person: &blankPers) } } } } struct PersonListView: View { var person: Person var body: some View{ NavigationLink(destination: InfoModal(person: person)){ VStack(alignment: .leading){ HStack{ if let _ = person.photo{ Image(uiImage: person.photo!) .resizable() .frame(width: 50, height: 50) .clipShape(Circle()) }else{ Image(systemName: "person.circle.fill") .clipShape(Circle()) .font(.system(size: 40)) } VStack(alignment: .leading){ HStack{ if person.name != ""{ Text(person.name) .font(.headline) } if person.surname != ""{ Text(person.surname) .font(.headline) if person.role != ""{ Text(person.role) .font(.subheadline) .foregroundColor(.secondary) } } } } } } } } } class Person : Identifiable { var name: String var surname: String var birthday: Date var phoneNumber: String var photo: UIImage? var email: String var id: UUID = UUID() var address: String var role: String init() { self.name = "" self.surname = "" self.birthday = Date() self.phoneNumber = "" self.email = "" self.address = "" self.role = "" } init(name: String, surname: String, birthday: Date, phoneNumber: String, email: String, address: String, role: String) { self.name = name self.surname = surname self.birthday = birthday self.phoneNumber = phoneNumber self.email = email self.address = address self.role = role } init(name: String, surname: String){ self.name = name self.surname = surname self.birthday = Date() self.phoneNumber = "" self.email = "" self.address = "" self.role = "" } init(name: String, surname: String, role: String){ self.name = name self.surname = surname self.birthday = Date() self.phoneNumber = "" self.email = "" self.address = "" self.role = role } init(name: String, surname: String, birthday: Date, phoneNumber: String, email: String, address: String, role: String, photo: UIImage?){ self.name = name self.surname = surname self.birthday = birthday self.phoneNumber = phoneNumber self.email = email self.address = address self.role = role self.photo = photo } } func ==(left: Person, right: Person) -> Bool{ if left.birthday == right.birthday && left.name == right.name && left.surname == right.surname && left.phoneNumber == right.phoneNumber && left.email == right.email && left.address == right.address && left.role == right.role{ return true } return false } func !=(left: Person, right: Person) -> Bool{ if left.name == right.name && left.surname == right.surname && left.birthday == right.birthday && left.phoneNumber == right.phoneNumber && left.email == right.email && left.address == right.address && left.role == right.role{ return true }else{ return false } } class Date { var date: String var month: String var year: String init(){ self.date = "" self.month = "" self.year = "" } init(date: String, month: String, year: String){ self.date = date self.month = month self.year = year } } func ==(left: Date, right: Date) -> Bool{ if left.date == right.date && left.month == right.month && left.year == right.year{ return true } return false } @Observable class PeopleStore{ var people: [Person] = [] }

And there is a code from app file:

import SwiftUI @main struct MyPeopleApp: App { @State private var peopleStore: PeopleStore = PeopleStore() var body: some Scene { WindowGroup { ContentView() .environment(peopleStore) } } }

I've tried to use onChange(of: person) but I don't know what to do in this method

This is a full code needed to run this app.

P.S.I've read many posts about it but I didn't find how to solve it.

Answer

The easy solution is to add @Observable to Person and Date

SwiftUI is very particular on how it detects redraws.

The better solution is to change them to a struct and leave the built in Equatable implementations.

I suggest watching "Demystify SwiftUI" and

Related Articles