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