SwiftUI scope of State variable - swiftui

I have a view that outputs rows of data using ForEach. The data is an array of defined data records from an FMDB database. I use List / ForEach and LazyVGrid to populate the rows in the view.
My goal is to take two of the output fields in the LazyVGrid and use onGesture to invoke one of two .sheet views for the details behind the value.
In the onGesture I want to capture the dataRecord used to populate that row and use the data in that record to call the view in .sheet
It all seems to work until I get to the .sheet(isPresented...
There the #State variable I populate in the onGesture is Nil
I'm putting in the code I use along with some print output that seems to show that the #State variable is populated in onGesture, but later in the .sheet(isPresented it is nil
I'm not sure I understand the scope of visibility for that #State variable and wonder if someone can help me figure this out...
MY CODE IS:
import SwiftUI
struct BudgetedIncomeView: View {
#State var account_code: Int
#State var budgetYear: Int
#State var budgetMonth: Int
#State private var isAdding = false
#State private var isEditing = false
#State private var isDeleting = false
#State private var budgetRec = BudgetedIncome.BudgetedIncomeRecord()
#State private var isBudgeted = false
#State private var isReceived = false
// Environment and ObservedObjects
#Environment(\.presentationMode) var presentationMode
#Environment(\.editMode) var editMode
#StateObject var toolbarViewModel = ToolbarViewModel()
var displayMonthYearString: String {
return calendarMonths(budgetMonth) + "-" + String(budgetYear)
}
let columns = [
GridItem(.flexible(), alignment: .leading),
GridItem(.fixed(UIScreen.main.bounds.width * 0.20), alignment: .trailing),
GridItem(.fixed(UIScreen.main.bounds.width * 0.20), alignment: .trailing)
]
let numberColor = Color.black
let negativeColor = Color.red
// Array of Budgeted Income records for View List
var budgetedIncome: [BudgetedIncome.BudgetedIncomeRecord] {
return BudgetedIncome.shared.selectBudgetedIncomeForAccountWithMonthAndYear(withAccountCode: self.account_code, withYear: self.budgetYear, withMonth: self.budgetMonth)
}
var body: some View {
ZStack {
VStack {
BudgetedIncomeHeader(headerText: "Budgeted")
.padding(.top, 10)
List {
ForEach (self.budgetedIncome, id: \.self) { budgetRecord in
LazyVGrid(columns: columns, spacing: 5) {
Text("\(budgetRecord.description)")
.lineLimit(1)
.padding(.leading, 5)
Text("\(NumberFormatter.formatWithComma(value: budgetRecord.income_budget))")
.foregroundColor((budgetRecord.income_budget < 0 ) ? self.negativeColor : self.numberColor)
.onTapGesture {
//
// PRINT STATEMENTS THAT SHOW THE DATA IS CAPTURED
//
let _ = print("budgetRecord in onGesture = \(budgetRecord)\n\n")
budgetRec = budgetRecord
let _ = print("budgetRec in onGesture = \(budgetRec)\n\n")
isBudgeted.toggle()
}
Text("\(NumberFormatter.formatWithComma(value: budgetRecord.income_received))")
.underline()
.padding(.trailing, 15)
}// END OF LAZYVGRID
} // END OF FOREACH
} // END OF LIST
BudgetedIncomeFooter(accountCode: $account_code, budgetYear: $budgetYear, budgetMonth: $budgetMonth)
.padding(.top, 5)
} // END OF VSTACK
.sheet(isPresented: $isBudgeted ){
//
// PRINT STATEMENT THAT SHOWS THE DATA IS NIL HERE
//
let _ = print("budgetRec in .sheet = \(budgetRec)\n\n")
BudgetedIncomeDetailsView(accountCode: budgetRec.account_code, incomeCode: budgetRec.income_code, budgetYear: budgetRec.budget_year, budgetMonth: budgetRec.budget_month)
.opacity(isBudgeted ? 1 : 0)
.zIndex(isBudgeted ? 1 : 0)
}
if isReceived {
BudgetedIncomeDetailsView(accountCode: 12345678, incomeCode: 50060, budgetYear: 2020, budgetMonth: 12)
.opacity(isReceived ? 1 : 0)
.zIndex(isReceived ? 1 : 0)
}
} // END OF ZSTACK
.navigationTitle("Budgeted Income for \(self.displayMonthYearString)")
.navigationBarBackButtonHidden(true)
.toolbar {
ToolBarCancelDeleteAdd() {
toolbarViewModel.cancelContent(editMode: editMode)
presentationMode.wrappedValue.dismiss()
}
adding: {
toolbarViewModel.addingContent(isAdding: &isAdding, editMode: editMode)
}
} // END OF TOOLBAR
} // END OF BODY VIEW
} // END OF STRUCT VIEW
struct BudgetedIncomeView_Previews: PreviewProvider {
static var previews: some View {
BudgetedIncomeView(account_code: 12345678,
budgetYear: 2020,
budgetMonth: 12)
.environmentObject(ApplicationSettings())
.environmentObject(Budget())
.environmentObject(GlobalSettings())
}
}
The output from these print statements is:
budgetRecord in onGesture = BudgetedIncomeRecord(income_id: Optional(589), account_code: Optional(12345678), income_code: Optional(50060), budget_year: Optional(2020), budget_month: Optional(12), description: Optional("ADD BACK SET ASIDE"), category: Optional("*Exclude From Reports"), income_budget: Optional(3600.0), income_received: Optional(3600.0), unexpected_income: Optional(0.0), category_code: Optional(99999), set_aside: Optional(true), set_aside_id: nil)
budgetRec in onGesture = BudgetedIncomeRecord(income_id: Optional(589), account_code: Optional(12345678), income_code: Optional(50060), budget_year: Optional(2020), budget_month: Optional(12), description: Optional("ADD BACK SET ASIDE"), category: Optional("*Exclude From Reports"), income_budget: Optional(3600.0), income_received: Optional(3600.0), unexpected_income: Optional(0.0), category_code: Optional(99999), set_aside: Optional(true), set_aside_id: nil)
budgetRec in .sheet = BudgetedIncomeRecord(income_id: nil, account_code: nil, income_code: nil, budget_year: nil, budget_month: nil, description: nil, category: nil, income_budget: nil, income_received: nil, unexpected_income: nil, category_code: nil, set_aside: nil, set_aside_id: nil)
It seems to me that somewhere the data goes nil. I don't seem to follow where the data is going out of scope???
I have used this technique in other views where I have used a view for the row and let the whole row be selected .onGesture.
I haven't tried it using LazyVGrid and selecting specific output values..
Any help would be greatly appreciated..
Bob

Related

How to use On appear() with SwiftUI on iOS 16

I didn't change anything in my code, but since iOS16 the onAppear() part is not working.
selectedParcoursIndexArray.append(0) does not execute,
And app crash on :
get: { self.selectedParcoursIndexArray[num] },
Because selectedParcoursIndexArray is empty. And should be filled while VStack appear.
My code :
import SwiftUI
struct DepartsSimultanesView: View {
#EnvironmentObject var objCourse : CourseActuelle
#EnvironmentObject var zindex : Zindex
#EnvironmentObject var listActuelle : ListActuelle
#State var selectedParcoursIndexArray : [Int] = [0]
#State var peutSauvegarder = false
#State var groupeManager : GroupeManager
var listeParcoursRestants : [String] = []
#State private var action: Int? = 0
var body: some View {
VStack{
HStack{
...
}
HStack{
...
}
Form{
Section(header: Text("Coureurs/Choix parcours")){
ForEach(0 ..< groupeManager.groupeList.count, id:\.self){ num in
let detailManager = DetailManager(groupeId: groupeManager.groupeList[num].id, courseId: objCourse.id!)
let listeParcoursRestants = listRestants(detailManager: detailManager)
let enCourse = detailManager.enCourseList.filter { $0.groupeId == groupeManager.groupeList[num].id }
Picker(selection: Binding(
get: { self.selectedParcoursIndexArray[num] },
set: { (newValue) in
self.selectedParcoursIndexArray[num] = newValue
peutSauvegarder = true
}), label: HStack{
Image(systemName: "person.fill")
Text("\(groupeManager.groupeList[num].nomGroupe)")
}.foregroundColor(selectedParcoursIndexArray[num] != 0 ? .orange : Color("ColorDarkLight"))
) {
if enCourse.count != 0 {
...
}
}.navigationBarHidden(true)
.disabled(listeParcoursRestants.count == 1 || enCourse.count != 0)
}
}
}
}
.onAppear(){
groupeManager = GroupeManager(courseId: objCourse.id!)
for _ in (0 ..< groupeManager.groupeList.count) {
selectedParcoursIndexArray.append(0)
}
}
.navigationBarHidden(true)
NavigationLink(destination: CestPartiView().environmentObject(objCourse), tag: 1, selection: $action){}
}
}
ForEach is not a for loop, it'll crash if you try to access an array by index inside its closure. id:\.self is a mistake, the id needs to be a property of the data it cannot be the data itself.
onAppear is designed to call an external action not to initialise the data that will be displayed. Move that initialisation somewhere else like to an #State struct's init.

Delete Func Deletes All Items in List - SwiftUI

When I select one of the items inside my list from my lists it only deletes the selected item.
But when I list all the lists and their reminders inside the AllView it deletes all of the reminders inside the list.
How can I overcome that problem?
To tell my problem clearly I have two videos that show both cases.
First Case
Second case
it is my delete button inside ReminderCell view
struct ReminderCell: View {
#Environment(\.managedObjectContext) private var viewContext
var reminder: CDReminder
#State var isSelected: Bool
Button(action: {
self.isSelected = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1){
deleteReminder(at: Int(reminder.index))
}
and again inside the ReminderCell I have deleteReminder func
func deleteReminder(at offsets: Int) {
viewContext.delete(reminder)
PersistenceController.shared.saveContext()
}
Inside the AllView I am calling listDetailCell as
struct AllView: View {
#State var title = ""
#State var note = ""
#State var releaseDate = Date()
#ObservedObject var list : CDListModel
#State var selectedList = CDListModel()
#Environment(\.presentationMode) var mode
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest var lists: FetchedResults<CDListModel>
init(){
list = CDListModel()
let request: NSFetchRequest<CDListModel> = CDListModel.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \CDListModel.name, ascending: true)]
request.entity = CDListModel.entity()
_lists = FetchRequest(fetchRequest: request)
}
var body: some View {
List{
ForEach(lists, id: \.self) { list in
ListDetailCell(list: list)
}
}
}
My ListDetailCell
struct ListDetailCell: View {
#State var title = ""
#ObservedObject var list : CDListModel
#State var selectedList: CDListModel!
#State var isAddReminderTapped = false
#Environment(\.managedObjectContext) private var viewContext
var body: some View {
VStack(alignment: .leading){
Text(list.text ?? "")
ForEach((list.reminders?.allObjects as! [CDReminder]).indices , id: \.self) { reminderIndex in
ReminderCell(reminder: (list.reminders?.allObjects[reminderIndex]) as! CDReminder, isSelected: false, selectedList: $selectedList, onComplete: {})
}
}
}
}
Your delete function is wrong.
Here you are passing an offsets: Int. But you are never using that offsets inside the function. You are just deleting the whole reminder.
func deleteReminder(at offsets: Int) {
viewContext.delete(reminder)
PersistenceController.shared.saveContext()
}
Somehow using ForEach inside the List was causing this problem in AllView.
When I change the body of the AllView like below my problem disappeared.
NavigationView {
ScrollView {
VStack{
HStack{
Text("Tumu")
.font(.system(size: 40, weight: .bold, design: .rounded))
.foregroundColor(.gray)
Spacer()
}
.padding(.leading)
ForEach(lists, id: \.self) { list in
ListDetailCell(list: list)
}

Don't Display Date if Same as Previous Entry

I have a list of entries each with an attached date. I would like to display the date only if there is a change in date. I first developed this software in iOS 14.4 that resulted in a view immutable error. This was because I was storing and changing a copy of the entry date.
Now in version iOS 14.5 I don't see the immutable error. But my software still doesn't work. If you run the code and look in the console you will note that Xcode is going through my six entries twice: the first time is always true (show the date) and the second time always false (don't show the date). Why?
In my actual code I am using dates of type Date instead of Strings in this example code. In my actual code, operation hangs as it loops endlessly through my function checkDate (Many times more than the number of entries). Does date of type Date include the time causing the compare to fail?
Is there a better way to prevent display of the date if it is the same as the previous entry?
struct KitchenItem: Codable, Identifiable {
var id = UUID()
var item: String
var itemDate: String
var itemCost: Double
}
class Pantry: ObservableObject {
#Published var oldDate: String = ""
#Published var kitchenItem: [KitchenItem]
init() {
self.kitchenItem = []
let item0 = KitchenItem(item: "String Beans", itemDate: "1/13/2021", itemCost: 4.85)
self.kitchenItem.append(item0)
let item1 = KitchenItem(item: "Tomatoes", itemDate: "1/22/2021", itemCost: 5.39)
self.kitchenItem.append(item1)
let item2 = KitchenItem(item: "Bread", itemDate: "1/22/2021", itemCost: 4.35)
self.kitchenItem.append(item2)
let item3 = KitchenItem(item: "Corn", itemDate: "3/18/2021", itemCost: 2.75)
self.kitchenItem.append(item3)
let item4 = KitchenItem(item: "Peas", itemDate: "3/18/2021", itemCost: 7.65)
self.kitchenItem.append(item4)
let item5 = KitchenItem(item: "Ice Cream", itemDate: "4/12/2021", itemCost: 7.95)
self.kitchenItem.append(item5)
}
}
struct ContentView: View {
#ObservedObject var pantry: Pantry = Pantry()
var body: some View {
LazyVStack (alignment: .leading) {
Text("Grandma's Food Pantry")
.font(.title)
.fontWeight(.bold)
.padding(.top, 36)
.padding(.leading, 36)
.padding(.bottom, 30)
ForEach(0..<pantry.kitchenItem.count, id: \.self) { item in
VStack (alignment: .leading) {
showRow(item: item)
}
}
}
}
}
struct showRow: View {
#ObservedObject var pantry: Pantry = Pantry()
var item: Int
var body: some View {
// don't show the date if is the same as the previous entry
let newDate = pantry.kitchenItem[item].itemDate
if checkDate(newDate: newDate) == true {
Text("\n\(newDate)")
.font(.title2)
.padding(.leading, 10)
}
HStack {
Text("\(pantry.kitchenItem[item].item)")
.padding(.leading, 50)
.frame(width: 150, alignment: .leading)
Text("\(pantry.kitchenItem[item].itemCost, specifier: "$%.2f")")
}
}
func checkDate(newDate: String) -> (Bool) {
print(" ")
print("new date = \(newDate)")
if newDate == pantry.oldDate {
print("false: don't show the date")
return false
} else {
pantry.oldDate = newDate
print("old date = \(pantry.oldDate)")
print("true: show the date")
return true
}
}
}
Actual code:
struct ListView: View {
#EnvironmentObject var categories: Categories
#EnvironmentObject var userData: UserData
#Environment(\.managedObjectContext) var viewContext
var money: String = ""
var xchRate: Double = 0.0
var cat: Int = 0
var mny: String = ""
#FetchRequest(
entity: CurrTrans.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \CurrTrans.entryDT, ascending: true)]
) var currTrans: FetchedResults<CurrTrans>
var body: some View {
GeometryReader { g in
ScrollView {
LazyVStack (alignment: .leading) {
TitleView()
ForEach(currTrans, id: \.self) { item in
showRow(item: item, priorDate: priorDate(forItemIndex: item), g: g)
}
.onDelete(perform: deleteItem)
}
.font(.body)
}
}
}
private func priorDate(forItemIndex item: Int) -> Date? {
guard item > 0 else { return nil }
return currTrans[item - 1].entryDT
}
}
struct showRow: View {
#EnvironmentObject var userData: UserData
var item: CurrTrans
var priorDate: Date?
var g: GeometryProxy
var payType = ["Cash/Debit", "Credit"]
var body: some View {
// don't show the date if is the same as the previous entry
let gotDate = item.entryDT ?? Date()
let newDate = gotDate.getFormattedDate()
Text("\(newDate)")
.opacity(gotDate == priorDate ? 0 : 1)
.font(.title2)
.padding(.leading, 10)
displays entry parameters in HStack...
Thou shalt not mutate thy data inside thy body method, for it is an abomination in the eyes of SwiftUI.
Modifying oldDate inside the body method is wrong. SwiftUI will get confused if you modify the data it is observing while it is rendering your views. Furthermore, SwiftUI doesn't make any guarantees about the order in which it renders the children of a LazyVStack (or any other container).
Is there a better way to prevent display of the date if it is the same as the previous entry?
Yes. Pass the current entry, and the prior entry's date, to the entry view.
Here's your data model and store, without the cruft:
struct KitchenItem: Codable, Identifiable {
var id = UUID()
var item: String
var itemDate: String
var itemCost: Double
}
class Pantry: ObservableObject {
#Published var kitchenItems: [KitchenItem] = [
.init(item: "String Beans", itemDate: "1/13/2021", itemCost: 4.85),
.init(item: "Tomatoes", itemDate: "1/22/2021", itemCost: 5.39),
.init(item: "Bread", itemDate: "1/22/2021", itemCost: 4.35),
.init(item: "Corn", itemDate: "3/18/2021", itemCost: 2.75),
.init(item: "Peas", itemDate: "3/18/2021", itemCost: 7.65),
.init(item: "Ice Cream", itemDate: "4/12/2021", itemCost: 7.95),
]
}
For each KitchenItem, you need to also extract the prior item's date, if there is a prior item. We'll use a helper method, priorDate(forItemIndex:), to do that. Also, you need to use StateObject, not ObservedObject, if you're going to create your store inside the view. Thus:
struct ContentView: View {
#StateObject var pantry: Pantry = Pantry()
var body: some View {
ScrollView(.vertical) {
LazyVStack (alignment: .leading) {
Text("Grandma's Food Pantry")
.font(.title)
.fontWeight(.bold)
.padding(.top, 36)
.padding(.leading, 36)
.padding(.bottom, 30)
ForEach(0 ..< pantry.kitchenItems.count) { i in
if i > 0 {
Divider()
}
KitchenItemRow(item: pantry.kitchenItems[i], priorDate: priorDate(forItemIndex: i))
}
}
}
}
private func priorDate(forItemIndex i: Int) -> String? {
guard i > 0 else { return nil }
return pantry.kitchenItems[i - 1].itemDate
}
}
Here is KitchenItemRow. You can see that it makes the date Text transparent if the date is the same as the prior item's date. I keep it in place but make it transparent so the row lays out the same:
struct KitchenItemRow: View {
var item: KitchenItem
var priorDate: String?
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(item.item)
Spacer()
Text("\(item.itemCost, specifier: "$%.2f")")
}
Text(item.itemDate)
.opacity(item.itemDate == priorDate ? 0 : 1)
}
.padding([.leading, .trailing], 10)
}
}
And here is TitleView, extracted from ContentView for hygiene:
struct TitleView: View {
var body: some View {
Text("Grandma's Food Pantry")
.font(.title)
.fontWeight(.bold)
.padding(.top, 36)
.padding(.leading, 36)
.padding(.bottom, 30)
}
}
Result:
UPDATE
Since your “real code” uses onDelete, it's important to give ForEach an id for each item instead of using the indexes.
Note that onDelete only works inside List, not inside LazyVStack.
So we need to map each item to its index, so we can find the prior item. Here's a revised version of my ContentView that uses onDelete:
struct ContentView: View {
#StateObject var pantry: Pantry = Pantry()
var body: some View {
let indexForItem: [UUID: Int] = .init(
uniqueKeysWithValues: pantry.kitchenItems.indices.map {
(pantry.kitchenItems[$0].id, $0) })
List {
TitleView()
ForEach(pantry.kitchenItems, id: \.id) { item in
let i = indexForItem[item.id]!
KitchenItemRow(item: item, priorDate: priorDate(forItemIndex: i))
}
.onDelete(perform: deleteItems(at:))
}
}
private func priorDate(forItemIndex i: Int) -> String? {
guard i > 0 else { return nil }
return pantry.kitchenItems[i - 1].itemDate
}
private func deleteItems(at offsets: IndexSet) {
pantry.kitchenItems.remove(atOffsets: offsets)
}
}
In my testing, this works and allows swipe-to-delete. I trust you can adapt it to your “real” code.

Display filename in next View too

I have a code that makes a http Request, gets an array with filenames from that, displays them each with an image and the filename below. Everything works fine.
Now I made each image a button that opens a detail page.
That works but at the top it should say the matching filename from the page before.
But I am not able to hand over the filename (name) from ContentView4 to the next page (ts).
The language is SwiftUi
Could you please help me?
Thanks
Nikias
Here is my code:
import SwiftUI
struct ContentView4: View {
#State var showingDetail = false
#State var username: String = "."
#State var password: String = "."
#State private var name = String("Nikias2")
#State private var t = String()
#State private var x = -1
#State var dateien = ["word.png"]
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
$showingDetail) {
ts(name: t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
doHttpRequest()
}
}
}
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
ContentView4()
}
}
struct ts: View {
#State var hin = false
#State var um = false
#State var datname: String = ""
var name: String
var body: some View {
NavigationView {
VStack {
Text(name)
.font(.system(size: 60))
.foregroundColor(.black)
.padding(50)
Button(action: {
self.hin.toggle()
}) {
Text("+")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.yellow)
.cornerRadius(35.0)
}
.padding()
if hin {
HStack {
Text("Datei auswählen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei hochladen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei herunterladen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.blue)
.cornerRadius(35.0)
Button(action: {
self.um.toggle()
}) {
Text("Datei umbenennen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.green)
.cornerRadius(35.0)
}
.padding()
if um {
HStack {
TextField(name, text: $datname)
.font(.headline)
.frame(width: 150, height: 70)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei umbenennen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.green)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei löschen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.red)
.cornerRadius(35.0)
}
}
}
}
I believe your issue is a result of using #State variables to store all of the attributes. #State variables are not consistent and get refreshed in the background by SwiftUI depending on your views visibility.
The piece that you are missing is a view controller class stored in an #EnviornmentObject variable. This class gets Initiated in your main contentView and is used to keep track and alter of all your attributes.
Each ContentView should reference the single #EnviornmentObject and pull data from that class.
Another solution which may work would be to replace all your #State variables with #StateObject vars. #StateObject vars are basically #State vars but get initiated before the struct get loaded and the value is kept consistent regardless of the view state of the parent struct.
Here is a rough implementation of #EnvironmentObject within your project.
Basically use the #EnvironmentObject to pass values to child views
ContentView4.swift
struct ContentView4: View {
#EnvironmentObject cv4Controller: ContentView4Controller
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< cv4Controller.dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = cv4Controller.dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(cv4Controller.t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
cv4Controller.$showingDetail) {
ts(name: cv4Controller.t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
cv4Controller.doHttpRequest()
}
}
}
ContentView4Controller.swift
class ContentView4Controller: ObservableObject {
#Published var showingDetail = false
#Published var username: String = "."
#Published var password: String = "."
#Published private var name = String("Nikias2")
#Published private var t = String()
#Published private var x = -1
#Published private var t = String()
#Published private var x = -1
#Published var dateien = ["word.png"]
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
Example of main ContentView.swift
struct ContentView: View {
var cv4Controller: ContentView4Controller = ContentView4Controller()
var body: some view {
// your main page output
GeometryReader { geo in
// just a guess for what you have in your main contentView
switch(page) {
case .main:
ContentView2()
default:
ContentView4()
break
}
}.environmentObject(cv4Controller) // this will make cv4Controller available to all child view structs
}
}
Add #Binding wrapper to the "name" variable in your ts view. And pass the t variable as a binding by adding a "$". This will keep your ts name variable updated to whatever is value it has in the parent view.
Also why do you use a NavigationView in your ts View?
struct ContentView4: View {
...
#State private var t = String()
...
var body: some View {
...
ZStack{
...
}
.sheet(isPresented: $showingDetail) {
ts(name: $t)
}
...
}
func doHttpRequest() {
...
}
}
struct ts: View {
...
#Binding var name: String
var body: some View {
...
}
}
My starting code works, but It's just displaying the Filenames in a row and if I tap a random image, the name won't fit, only if I'm going down in the row and tap them. The problem is, that I don't know how to set the variable to the id, not to pass them to the next view. Has anyone got and idea how I can pass the right filename into a variable in the for loop and read it in the next view?

SwiftUI action at deselection of a Lists row

I have a SwiftUI List, that changes an attribute on a row, e.g. color on a tap.
Now I want to start an action e.g. reset the color, if another row is tapped.
I´m looking for an event, that the row receives ,if it is deselected.
Here my example code:
struct ContentView: View {
#State var data : [String] = ["first","second","third","4th","5th"]
var body: some View {
List {
ForEach (data, id: \.self) {
item in
ColoredRow(text: item)
}
}
}
}
struct ColoredRow: View {
var text: String = ""
#State var col : Color = Color.white
var body: some View{
Text("\(text)")
.background(col)
.onTapGesture {
self.col = Color.red
}
// .onDeselect {
// print("disappeare \(self.text)")
// self.col = Color.white
// }
}
}
Let' recall that SwiftUI is reactive (ie. state-driven, not event-driven), so if we wan't to change something in UI we need to find a way to change it via state (either UI or model, but state).
So, below is slightly modified your code to show possible approach. Tested with Xcode 11.2 / iOS 13.2.
struct ContentView: View {
#State var data : [String] = ["first","second","third","4th","5th"]
#State private var selectedItem: String? = nil
var body: some View {
List {
ForEach (data, id: \.self) {
item in
ColoredRow(text: item, selection: self.$selectedItem)
}
}
}
}
struct ColoredRow: View {
var text: String = ""
#Binding var selection: String?
#State var col : Color = Color.white
var body: some View{
Text("\(text)")
.background(selection == text ? Color.red : Color.white)
.onTapGesture {
self.selection = (self.selection == self.text ? nil : self.text)
}
}
}