This question already has answers here:
What is the difference between ObservedObject and StateObject in SwiftUI
(6 answers)
Saving Date/Time to UserDefaults through didSet on #Published var (from TimePicker component of SwiftUI)
(1 answer)
Closed 8 months ago.
I trying to update the array when the user change the input.
Here is the viewModel
struct Person: Identifiable {
var id = UUID()
var name : String
var age : String
}
class PersonViewModel : ObservableObject{
#Published var PersonArray = [Person]()
func emptyPersonArray() {
self.PersonArray.append(Person(name: "", age: ""))
}
}
struct ContentView: View {
#ObservedObject var personViewModel = PersonViewModel()
var body: some View {
VStack{
Button(action: {
personViewModel.emptyPersonArray()
}) {
HStack {
Text("Add")
.font(.title3)
.bold()
}
}
.padding()
.foregroundColor(Color.black)
.frame(width: 150.72, height: 40)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color(red: 0.463, green: 0.483, blue: 1.034), lineWidth: 2))
List{
ForEach(personViewModel.PersonArray) {person in
PersonView(person: person,name: person.name, age: person.age)
}.padding(.leading, -150)
}
Button(action: {
for person in personViewModel.PersonArray{
print("Person Name: \(person.name)")
print("Person Age: \(person.age)")
}
}) {
HStack {
Text("Show data")
.font(.title3)
.bold()
}
}
}
}
}
here is PersonView:
struct PersonView: View {
#State var person: Person
#State var name = ""
#State var age = ""
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 9)
.fill(Color.white)
.frame(width: 650, height: 180)
VStack (alignment: .center) {
Text("Person")
.font(.title3)
.fontWeight(.bold)
VStack{
Text("Enter Name:")
TextField("", text: $name)
.onChange(of: name, perform: { newValue in
person.name = newValue
})
.frame(width: 289, height: 40.0)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color.black))
Text("Enter Age:")
TextField("", text: $age).padding()
.onChange(of: age, perform: { newValue in
person.age = newValue
})
.frame(width: 289, height: 40.0)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color.black))
} .padding(.leading)
}
}
}
}
when I tried to print it shows empty and did not update:
The problem have be solved
by changing the #State to #Binding in PersonView
struct PersonView: View {
#Binding var person: Person
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 9)
.fill(Color.white)
.frame(width: 650, height: 180)
VStack (alignment: .center) {
Text("Person")
.font(.title3)
.fontWeight(.bold)
VStack{
Text("Enter Name:")
TextField("", text: $person.name)
.onChange(of: person.name, perform: { newValue in
person.name = newValue
})
.frame(width: 289, height: 40.0)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color.black))
Text("Enter Age:")
TextField("", text: $person.age).padding()
.onChange(of: person.age, perform: { newValue in
person.age = newValue
})
.frame(width: 289, height: 40.0)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color.black))
} .padding(.leading)
}
}
}
}
Related
I have a problem that when I write in the first textFiled it be repeated in the rest textfields, like it's shown in the picture:
And here is my code:
First here is the model and viewModel:
struct Person: Identifiable {
var id: String
var name : String
var age : String
}
class PersonViewModel : ObservableObject{
#Published var PersonArray = [Person]()
func emptyPersonArray() {
self.PersonArray.append(Person(id: "", name: "", age: ""))
}
}
and here is my view:
struct ContentView: View {
#ObservedObject var personViewModel = PersonViewModel()
var body: some View {
VStack{
Button(action: {
personViewModel.emptyPersonArray()
}) {
HStack {
Text("Add")
.font(.title3)
.bold()
}
}
.padding()
.foregroundColor(Color.black)
.frame(width: 150.72, height: 40)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color(red: 0.463, green: 0.483, blue: 1.034), lineWidth: 2))
List{
ForEach(personViewModel.PersonArray) {person in
PersonView(name: person.name, age: person.age)
}
}
}
}
}
Finally, here is the PersonView:
struct PersonView: View {
#State var name = ""
#State var age = ""
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 9)
.fill(Color.white)
.frame(width: 650, height: 180)
VStack (alignment: .center) {
Text("Person")
.font(.title3)
.fontWeight(.bold)
VStack{
Text("Enter Name:")
TextField("", text: $name)
.frame(width: 289, height: 40.0)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color.black))
Text("Enter Age:")
TextField("", text: $age).padding()
.frame(width: 289, height: 40.0)
.background(RoundedRectangle(cornerRadius: 6).stroke(Color.black))
} .padding(.leading)
}
}
}
}
Yes, because you are giving the same id to all the persons each time you instantiate a new object, and you basically end up modifying the same person. Try giving a UUID like this to the Person:
struct Person: Identifiable {
var id: UUID = UUID()
var name : String
var age : String
}
class PersonViewModel : ObservableObject{
#Published var PersonArray = [Person]()
func emptyPersonArray() {
self.PersonArray.append(Person(name: "", age: ""))
}
}
So I created this project to learn Swift Ui but I am stuck. I can't figure out why the HomeView isn't displaying the name of the contact. I created the ContactModel to store the contact details and the ContactViewModel to store all of the contacts in userDefaults.I stored the information in UserDefaults, but for some reason it just isn't working. I added all my files from the Xcode project. Thanks for the help!
#main
struct ContactsAppApp: App {
#StateObject var contactViewModel: ContactViewModel = ContactViewModel()
var body: some Scene {
WindowGroup {
NavigationView{
ContentView()
}
.environmentObject(contactViewModel)
}
}
}
struct ContentView: View {
var body: some View {
TabView{
HomeView()
.tabItem {
Image(systemName: "house")
.foregroundColor(.white)
Text("home")
}
AddContactView()
.tabItem{
Image(systemName: "laptopcomputer")
Text("work")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContactModel: Identifiable, Codable {
let id: String
let name: String
let phoneNumber: String
let email: String
init(id: String = UUID().uuidString, name: String, phoneNumber: String, email: String) {
self.id = id
self.name = name
self.phoneNumber = phoneNumber
self.email = email
}
func updateCompletion() -> ContactModel {
return ContactModel(id: id, name: name, phoneNumber: phoneNumber, email: email)
}
}
class ContactViewModel: ObservableObject {
#Published var items: [ContactModel] = [] {
didSet {
saveItems()
}
}
let itemsKey: String = "items_list"
init() {
getItems()
}
func getItems() {
guard
let data = UserDefaults.standard.data(forKey: itemsKey),
let savedItems = try? JSONDecoder().decode([ContactModel].self, from: data)
else { return }
self.items = savedItems
}
func deleteItem(indexSet: IndexSet) {
items.remove(atOffsets: indexSet)
}
func moveItem(from: IndexSet, to: Int) {
items.move(fromOffsets: from, toOffset: to)
}
func addItem(name: String, phoneNumber: String, email: String) {
let newItem = ContactModel(name: name, phoneNumber: phoneNumber, email: email)
items.append(newItem)
}
func updateItem(item: ContactModel) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index] = item.updateCompletion()
}
}
func saveItems() {
if let encodedData = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(encodedData, forKey: itemsKey)
}
}
}
import SwiftUI
struct HomeView: View {
#EnvironmentObject var contactViewModel: ContactViewModel
var body: some View {
NavigationView {
VStack {
HStack{
Text("Contacts")
.font(.system(size: 35, weight: .bold))
.padding(.leading,30)
.padding(.top,20)
.foregroundColor(.white)
Spacer()
}
Spacer()
ScrollView{
ForEach(contactViewModel.items) { item in
contactWidget(name: item.name)
}
}
.padding(.top,20)
Button(action: {
print("touched")
}, label: {
NavigationLink(destination: AddContactView().navigationBarBackButtonHidden(true)) {
HStack{
ZStack{
RoundedRectangle(cornerRadius: 40)
.frame(width: 200, height: 50)
.foregroundColor(.blue)
Text("Add Contact")
.foregroundColor(.white)
}
}
}
.navigationBarHidden(true)
})
}
.background(.black)
}
.navigationBarHidden(true)
}
}
struct contactWidget : View{
let name: String
var body: some View{
ZStack {
RoundedRectangle(cornerRadius: 20)
.frame(width: 350, height: 100)
.foregroundColor(.gray)
HStack {
Image(systemName: "person")
.resizable()
.clipShape(Circle())
.frame(width: 40, height: 40)
Text(name)
.font(.system(size: 18, weight: .semibold))
Spacer()
HStack{
Circle()
.frame(width: 40, height: 40)
.foregroundColor(.orange)
Circle()
.frame(width: 40, height: 40)
.foregroundColor(.blue)
Circle()
.frame(width: 40, height: 40)
.foregroundColor(.pink)
}
}
.padding(.leading,40)
.padding(.trailing,30)
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
HomeView()
}
.environmentObject(ContactViewModel())
}
}
import SwiftUI
struct AddContactView: View {
#EnvironmentObject var contactViewModel: ContactViewModel
#State var contactName: String = ""
#State var phoneNumber: String = ""
#State var email: String = ""
var body: some View {
NavigationView {
VStack {
HStack{
Text("Contacts")
.font(.system(size: 35, weight: .bold))
.padding(.leading,30)
.padding(.top,20)
.foregroundColor(.white)
Spacer()
}
TextField("Enter name", text: $contactName)
.frame(width: 330, height: 30)
.padding()
.background(.white)
.cornerRadius(10)
TextField("Enter Phone Number", text: $phoneNumber)
.frame(width: 330, height: 30)
.padding()
.background(.white)
.cornerRadius(10)
TextField("Enter Email", text: $email)
.frame(width: 330, height: 30)
.padding()
.background(.white)
.cornerRadius(10)
Text(contactName)
.foregroundColor(.white)
Spacer()
.padding(.top,20)
Button(action: {
contactViewModel.addItem(name: contactName, phoneNumber: phoneNumber, email: email)
contactName = ""
phoneNumber = ""
email = ""
}, label: {
NavigationLink(destination: HomeView().navigationBarBackButtonHidden(true)) {
HStack{
ZStack{
RoundedRectangle(cornerRadius: 40)
.frame(width: 200, height: 50)
.foregroundColor(.blue)
Text("Done")
.foregroundColor(.white)
}
}
}
.navigationBarHidden(true)
})
}
.background(.black)
}
.navigationBarHidden(true)
}
}
struct AddContactView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
AddContactView()
}
.environmentObject(ContactViewModel())
}
}
The reason "...why the HomeView isn't displaying the name of the contact..." is
because you have NavigationLink(destination: HomeView().... inside the button label in AddContactView. That is, the tap is caught by the NavigationLink
and goes to the HomeView without doing the button action
of adding the contact, contactViewModel.addItem( ...). So restructure your code with the
NavigationLink outside of it.
The example code below should fix your problem.
struct AddContactView: View {
#EnvironmentObject var contactViewModel: ContactViewModel
#State var contactName: String = ""
#State var phoneNumber: String = ""
#State var email: String = ""
#State var goHome = false // <-- here
var body: some View {
NavigationView {
VStack {
HStack{
Text("Contacts")
.font(.system(size: 35, weight: .bold))
.padding(.leading,30)
.padding(.top,20)
.foregroundColor(.white)
Spacer()
}
TextField("Enter name", text: $contactName)
.frame(width: 330, height: 30)
.padding()
.background(.white)
.cornerRadius(10)
TextField("Enter Phone Number", text: $phoneNumber)
.frame(width: 330, height: 30)
.padding()
.background(.white)
.cornerRadius(10)
TextField("Enter Email", text: $email)
.frame(width: 330, height: 30)
.padding()
.background(.white)
.cornerRadius(10)
Text(contactName).foregroundColor(.white)
Spacer().padding(.top,20)
Button(action: {
contactViewModel.addItem(name: contactName, phoneNumber: phoneNumber, email: email)
contactName = ""
phoneNumber = ""
email = ""
goHome = true // <-- here
}, label: {
HStack{
ZStack{
RoundedRectangle(cornerRadius: 40)
.frame(width: 200, height: 50)
.foregroundColor(.blue)
Text("Done")
.foregroundColor(.white)
}
}
})
// -- here
NavigationLink(destination: HomeView().navigationBarBackButtonHidden(true), isActive: $goHome) {
EmptyView()
}.navigationBarHidden(true)
}
.background(.black)
}
.navigationBarHidden(true)
}
}
Similarly for the button in HomeView, or remove the Button part, just keep the NavigationLink
This are my code.
Im Creating Cash Register IPAD
What is see:
When I tap the item twice with the same item name it adds another
row instead
What I expect:
My expectation is it only adds one row of item and its quantity counter
Hope Someone can help me. I already research cannot find sample or any info to direct for what I want to achieved. Thanks all
import Foundation
import SwiftUI
class OrderViewModel: ObservableObject {
#Published var orderItems = [Cart]()
#State var count: Int = 1
func AddToCart(item: item){
orderItems.append(
Cart(product_name: item.product_name, price:item.price, quantity: quantity )
)
}
}
struct Cart: Identifiable{
var id = UUID()
var product_name: String
var price: String
var quantity: Int
}
This are my cartView the will print all #EnvironmentObject from class OrderViewModel
import SwiftUI
struct CartContainer: View {
#EnvironmentObject var order: OrderViewModel
var body: some View{
ZStack{
Color("lightGrayColor")
.ignoresSafeArea()
VStack(alignment: .leading) {
VStack{
HStack{
Text("Item Name")
.font(Font.system(size: 16, weight: .light))
.frame(width:200)
Spacer()
Text("Qty.").font(Font.system(size: 16, weight: .light))
.padding(.leading, -78)
Spacer()
Text("Subtotal.").font(Font.system(size: 16, weight: .light))
.padding(.leading, -78)
}
}
.padding(.top, 10)
ScrollView{
VStack(alignment: .leading){
ForEach(order.orderItems, id:\.id){ list in
HStack{
Text(list.product_name)
.font(Font.system(size: 16, weight: .light))
.frame(width:200)
Spacer()
Text(list.price).font(Font.system(size: 16, weight: .light))
.padding(.leading, -78)
Spacer()
Text("\(list.quantity)").font(Font.system(size: 16, weight: .light))
.padding(.leading, -78)
}
.padding(.top, 10)
}
}.padding()
}.padding()
Text("Grand Total :")
.font(Font.system(size: 30, weight: .bold))
.padding(.bottom, 30)
.padding(.leading, 40)
Button(action: {
}) {
Text("Charge")
.foregroundColor(.white)
.font(Font.system(size: 30, weight: .bold))
.frame(minWidth: 0, maxWidth: .infinity)
.padding(.top, 35)
.padding(.bottom, 35)
.background(Color("ButtonGreen"))
}.padding(.bottom, -10)
}
}
.frame(width: 550)
}
}
struct CartContainer_Previews: PreviewProvider {
static var previews: some View {
CartContainer().environmentObject(OrderViewModel())
}
}
And this are the Product List which I pullout from API
import SwiftUI
struct ProductsListContainer: View {
var body: some View{
ZStack{
Color(.white)
VStack{
ProductList()
}
}
.frame(maxWidth: .infinity)
}
}
struct ProductList: View{
var body: some View{
VStack {
SearchItem()
ItemListHeader()
ShowItemList()
.padding(5)
}
}
}
struct ItemListHeader: View {
var body: some View {
HStack{
Text("Item Name")
.font(Font.system(size: 20, weight: .heavy))
.frame(width:300)
Spacer()
Text("Price").font(Font.system(size: 20, weight: .heavy))
.padding(.leading, -78)
Spacer()
Text("Qty.").font(Font.system(size: 20, weight: .heavy))
.padding(.leading, -78)
}
Divider()
}
}
struct ShowItemList: View {
#StateObject var itemVM = ProductViewListModel()
#EnvironmentObject var order: OrderViewModel
var body: some View {
ScrollView {
ForEach(itemVM.item) { dataPoint in
ZStack{
HStack{
Text(dataPoint.product_name)
.font(.headline)
.frame(width:300)
Spacer()
Text(dataPoint.price)
Spacer()
Text("\(dataPoint.quantity)")
}
.padding()
.padding(.leading, -20)
.padding(.trailing, 20)
.padding(.top, 5)
.padding(.bottom, 5)
}.onTapGesture {
self.order.AddToCart(item: dataPoint.product)
}
Divider()
}
}
.padding()
}
}
When you use "AddToCart(...)", you need to check if you have the item already.
Try something similar to this:
class OrderViewModel: ObservableObject {
#Published var orderItems = [Cart]()
// note: comparing product_name, assuming product_name is unique here
func AddToCart(item: Item) {
if let ndx = orderItems.firstIndex(where: {$0.product_name == item.product_name}) {
// already have this cart/item, do an update here
orderItems[ndx].price = item.price
orderItems[ndx].quantity = item.quantity
} else {
// add a new cart/item
orderItems.append(Cart(product_name: item.product_name, price:item.price, quantity: item.quantity))
}
}
}
and probably call it like this:
.onTapGesture {
self.order.AddToCart(item: dataPoint)
}
I hope you are having a more pleasant evening than mine!
So as I mentioned in the title, my for each loop crashes whenever I try to remove an item from the original list with a binding. I did some research and the problem is that for each generates a view with an id but when you delete the item in your child view it can't find the contents and crashes. Returns 'Thread 1: Fatal error: Index out of range'. I can fix the issue by declaring a #State var instead of #Binding, which really works! However, I have more than a delete button in my child view and if I don't use a binding declaration, changes made don't reflect on the main view. I don't wanna give up on neither the delete button nor buttons. Is there way to keep all of them in my childview?
Mainview declarations;
struct ContentView: View {
#ObservedObject var superReminders = SuperReminders()
#State var superReminder = SuperReminder()}
My list View;
List{
ForEach(superReminders.reminderlist.indices, id: \.self) { index in
NavigationLink(destination: DetailedRemView(superReminder : self.$superReminders.reminderlist[index] ).environmentObject(superReminders)) {
squareImageView(superReminder : self.$superReminders.reminderlist[index]).environmentObject(superReminders).environmentObject(superReminders)
}.listRowBackground(Color.clear)
}.onDelete { indexSet in
superReminders.reminderlist.remove(atOffsets: indexSet)}
}
Childview declarations;
import SwiftUI
struct DetailedRemView: View {
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "EE, MMM d, YYYY"
return formatter
}
#State public var showingDetail = false
#Environment(\.colorScheme) var colorScheme: ColorScheme
#State private var deleteReminderAlert = false
#EnvironmentObject var superReminders : SuperReminders
#Environment(\.presentationMode) var presentationMode
#Binding var superReminder : SuperReminder
#State private var showDialog = false
#State var animate = false
var body: some View {
VStack{
HStack(alignment: .center){
Text(superReminder.remdate)
.font(.title)
.multilineTextAlignment(.leading)
.padding(.leading)
.frame(minWidth: 100,maxWidth: .infinity, maxHeight: 50)
Spacer()
Button(action: {
self.showDialog.toggle()
}, label: {
ZStack{
RoundedRectangle(cornerRadius: 10)
.fill(Color.blue)
.frame(width: 80, height: 35)
HStack{
Text("Edit")
.foregroundColor(.white)
.multilineTextAlignment(.center)
.cornerRadius(8)
Image(systemName: "pencil")
.foregroundColor(.white)
}
}
.shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
.padding(.leading)
.alert(isPresented: $showDialog,
TextAlert(title: "Edit reminder title",
message: "Enter a new title or dissmis.", placeholder: superReminder.remdate,
keyboardType: .default) { result in
if let text = result {
if text != "" {
superReminder.remdate = text }
else{}
} else {
}
})
})
.padding(.leading)
}
.frame(minWidth: 100, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, maxHeight: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.padding(.vertical, -10)
ZStack(alignment: .topTrailing){
Image(superReminder.image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0,maxWidth: .infinity,minHeight: 200,maxHeight: .infinity)
.saturation(superReminder.pastreminder ? 0.1 : 1)
.clipShape(Rectangle())
.cornerRadius(10)
.padding(.all)
.pinchToZoom()
HStack{
Text(superReminder.dateactual, formatter: dateFormatter)
.foregroundColor(.white)
.frame(width: 180, height: 30, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.background( superReminder.pastreminder ? Color.gray : Color.lightlygreen)
.cornerRadius(8)
.animation(/*#START_MENU_TOKEN#*/.easeIn/*#END_MENU_TOKEN#*/)
if superReminder.pastreminder == true {
ZStack{
RoundedRectangle(cornerRadius: 8)
.fill(Color.black)
.frame(width: 30, height: 30)
Image(systemName: "moon.zzz")
.foregroundColor(.white)
}.offset(x: animate ? -3 : 0)
.onAppear(perform: {
shake()
})
}
else{}
}
.zIndex(0)
.offset(x: -10, y: 10)
.padding()
}
.zIndex(1)
.shadow(color: Color.gray.opacity(0.4), radius: 3, x: 1, y: 2)
HStack{
Button(action: {
self.showingDetail.toggle()
}){
ZStack{
RoundedRectangle(cornerRadius: 10)
.fill(Color.lightlygreen)
.frame(width: 140, height: 40)
HStack{
Text("Reschedule")
.foregroundColor(.white)
.multilineTextAlignment(.center)
.cornerRadius(8)
Image(systemName: "calendar")
.foregroundColor(.white)
}
}
.shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
.padding(.all, 4.0)
}
.sheet(isPresented: $showingDetail, content :{
remdatepicker(isPresented: self.$showingDetail, superReminder: $superReminder)})
Button(action: {
if superReminder.pastreminder == true {
superReminder.pastreminder = false
}
else if superReminder.pastreminder == false{
superReminder.pastreminder = true
}
}, label: {
ZStack{
RoundedRectangle(cornerRadius: 10)
.fill(superReminder.pastreminder == true ? Color.lightlyblue : Color.gray)
.frame(width: 100, height: 40)
HStack{
Text(superReminder.pastreminder == true ? "Activate" : "Silence")
.foregroundColor(.white)
.multilineTextAlignment(.center)
.cornerRadius(8)
Image(systemName: superReminder.pastreminder == true ? "checkmark.circle" : "moon.zzz")
.foregroundColor(.white)
}
}
.shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
.padding(.all, 4.0)
})
Button(action: {
self.deleteReminderAlert.toggle()
}, label: {
ZStack{
RoundedRectangle(cornerRadius: 10)
.fill(Color(.red))
.frame(width: 40, height: 40)
HStack{
Image(systemName: "trash")
.foregroundColor(.white)
}
}
.shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
.padding(.all, 4.0)
})
}.padding(.bottom, 20)
.alert(isPresented: $deleteReminderAlert){
Alert(
title: Text("Are you sure?"),
message: Text("Do you want to delete this reminder?"),
primaryButton: .destructive(Text("Yes"), action: {
superReminders.remove(superReminder: superReminder)
self.presentationMode.wrappedValue.dismiss()
}),
secondaryButton: .cancel(Text("No"))
)
}
}
}
func shake() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation(Animation.default.repeatCount(6).speed(7)){
animate.toggle()}}}
}
Class and List;
import SwiftUI
struct SuperReminder: Identifiable, Codable, Equatable {
var id = UUID()
var remdate = ""
var dateactual = Date.init()
var image = "New1"
var pastreminder = false
}
class SuperReminders: ObservableObject {
#Published var reminderlist: [SuperReminder]
init() {
self.reminderlist = [
]
}
func add(superReminder: SuperReminder) {
reminderlist.append(superReminder)
}
func remove(superReminder: SuperReminder) {
if let index = reminderlist.firstIndex(of: superReminder) {
reminderlist.remove(at: index)
}
}
}
This answer is similar to
Accessing and manipulating array item in an EnvironmentObject
Loop over superReminders.reminderlist since SuperReminder: Identifiable, Codable, Equatable.
ForEach(superReminders.reminderlist) { superReminder in
NavigationLink(destination: DetailedRemView(superReminders: superReminders,
superReminder: superReminder)) {
-----
}
}
In DetailedRemView, do the following:
struct DetailedRemView: View {
#ObservedObject var superReminders : SuperReminders
var superReminder : SuperReminder
// find index of current superReminder
var indexOfReminder: Int? {
superReminders.reminderlist.firstIndex {$0 == superReminder}
}
var body: some View {
// Unwrap indexOfReminder
if let index = indexOfReminder {
VStack {
------
}
}
}
----
}
Use superReminders.reminderlist[index] in DetailRemView whereever you need to update superReminder.
superReminders.reminderlist[index].pastreminder = false
I'm trying to build a custom list where the user can select an entry and the row will expand and show a picker. This picker should update an object (TimeItem) which stores the time information.
However, I was not able to use Binding in the ForEach Loop with Picker and I don't know why. The error message in Xcode is "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions".
I also tried to use ForEach(Array(items.enumerated()), id: \.1) instead of ForEach(items) to get the index of the current row but that would mess up the delete animation (but only sometimes!?).
I do not want to use the same Binding for each row (for ex. self.$selectedElement.minutes) - every row should have its own Binding.
Does anybody know how to fix this issue? Thanks for helping!
class TimeItem: Identifiable, Equatable, ObservableObject {
static func == (lhs: TimeItem, rhs: TimeItem) -> Bool {
lhs.id == rhs.id
}
let id = UUID()
#Published var minutes: Int = 0
#Published var seconds: Int = 30
}
struct ContentView: View {
#State var items = [TimeItem]()
#State var selectedElement: TimeItem?
var body: some View {
ScrollView(){
VStack{
ForEach(items){ elem in
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: elem == selectedElement ? 120 : 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("\(elem.minutes)")
.opacity(elem == selectedElement ? 0 : 1)
.transition(AnyTransition.scale)
if(elem == selectedElement){
HStack{
Picker(selection: elem.$minutes, label: Text("")){ // <- I can't use Binding with "elem"
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
Picker(selection: .constant(0), label: Text("")){
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
}
.frame(height: 120)
.clipped()
}
HStack{
Button(action: {
self.items.removeAll { $0.id == elem.id }
})
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = elem
}
}
}
}
Spacer()
Button(action: {
self.items.append(TimeItem())
})
{
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}.animation(.spring(), value: items)
}
}
That case when you should do what compiler said: break up expression (ie. that big view) into distinct sub-expressions (ie. smaller subviews)
Here is fixed components (tested with Xcode 11.4 / iOS 13.4)
struct ContentView: View {
#State var items = [TimeItem]()
#State var selectedElement: TimeItem?
var body: some View {
ScrollView(){
VStack{
ForEach(items){ elem in
ItemRowView(elem: elem, selectedElement: self.$selectedElement){
self.items.removeAll { $0.id == elem.id }
}
}
}
Spacer()
AddItemView {
self.items.append(TimeItem())
}
}.animation(.spring(), value: items)
}
}
struct SelectedElementView: View {
#ObservedObject var elem: TimeItem
var body: some View {
HStack{
Picker(selection: $elem.minutes, label: Text("")){
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
Picker(selection: .constant(0), label: Text("")){
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
}
.frame(height: 120)
.clipped()
}
}
struct AddItemView: View {
let action: ()->()
var body: some View {
Button(action: action)
{
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}
}
struct ItemRowView: View {
#ObservedObject var elem: TimeItem
#Binding var selectedElement: TimeItem?
let action: ()->()
var body: some View {
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: elem == selectedElement ? 120 : 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("\(elem.minutes)")
.opacity(elem == selectedElement ? 0 : 1)
.transition(AnyTransition.scale)
if(elem == selectedElement){
SelectedElementView(elem: elem)
}
HStack{
Button(action: action)
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = self.elem
}
}
}
}