explanation of the following code step by step

Hi all, could you please explain extreme detail what this code is doing exactly, because it's working on my APP but not very certain of this Tupple

 ForEach(Array(filteredNonTECOProjects.enumerated()), id: \.offset) { (row, project) in
 TimeKeyInRow(id: project.id, project: project, isSet: $isSet[row])

when.I look at the documentation of swift is seems like I'm using the TUPPLE within another tupple... I I'm lost hahahaha

Answered by Claude31 in 779943022

Let's decompose this code:

ForEach(Array(filteredNonTECOProjects.enumerated()), id: \.offset) {
    (row, project) in TimeKeyInRow(id: project.id, project: project, isSet: $isSet[row])
}
  • ForEach(x) loops through all the elements of x

In this case, they are the elements of the Array.

The array is built from filteredNonTECOProjects

filteredNonTECOProjects is enumerated(), which means the index of the array is retrieved and associated to the element of the array itself, to get a tuple: (row, project).

id: \ .offset defines the id needed by ForEach which requires identifier (identifiable conformance). In the case, the offset of element in the array is used as identifier.

So at the end, ForEach loops through the array and pass (row, project) to the closure that is executed at each step of ForEach.

  • The closure
{ (row, project) in TimeKeyInRow(id: project.id, project: project, isSet: $isSet[row])  }

for each (row, project), we create a TimeKeyInRow view that will be displayed (either in VStack or List or whatever else).

It passes the parameters required by TimeKeyInRow: id, project, isSet for the row.

isSet[row] requires $, because TimeKeyInRow expects a Binding.

  • Note on Binding.

When you pass a parameter (isSet[row] to TimeKeyInRow) and this parameter will be modified in TimeKeyInRow, you need to retrieve the new value of isSet[row] in the caller, in the State var

@State var isSet: [Bool]

So, by using $, you pass a pointer to the var, and so the original var is modified when you change in TimeKeyInRow.

It is the equivalent of inout parameter for plain Swift: Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead. You write an in-out parameter by placing the inout keyword right before a parameter’s type. An in-out parameter has a value that’s passed in to the function, is modified by the function, and is passed back out of the function to replace the original value.

func swapTwoInts(_ a: inout Int, _ b: inout Int) 

// You call with an & sign (instead of $ sign)

swapTwoInts(&someInt, &anotherInt)

Home that helps.

Accepted Answer

Let's decompose this code:

ForEach(Array(filteredNonTECOProjects.enumerated()), id: \.offset) {
    (row, project) in TimeKeyInRow(id: project.id, project: project, isSet: $isSet[row])
}
  • ForEach(x) loops through all the elements of x

In this case, they are the elements of the Array.

The array is built from filteredNonTECOProjects

filteredNonTECOProjects is enumerated(), which means the index of the array is retrieved and associated to the element of the array itself, to get a tuple: (row, project).

id: \ .offset defines the id needed by ForEach which requires identifier (identifiable conformance). In the case, the offset of element in the array is used as identifier.

So at the end, ForEach loops through the array and pass (row, project) to the closure that is executed at each step of ForEach.

  • The closure
{ (row, project) in TimeKeyInRow(id: project.id, project: project, isSet: $isSet[row])  }

for each (row, project), we create a TimeKeyInRow view that will be displayed (either in VStack or List or whatever else).

It passes the parameters required by TimeKeyInRow: id, project, isSet for the row.

isSet[row] requires $, because TimeKeyInRow expects a Binding.

  • Note on Binding.

When you pass a parameter (isSet[row] to TimeKeyInRow) and this parameter will be modified in TimeKeyInRow, you need to retrieve the new value of isSet[row] in the caller, in the State var

@State var isSet: [Bool]

So, by using $, you pass a pointer to the var, and so the original var is modified when you change in TimeKeyInRow.

It is the equivalent of inout parameter for plain Swift: Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead. You write an in-out parameter by placing the inout keyword right before a parameter’s type. An in-out parameter has a value that’s passed in to the function, is modified by the function, and is passed back out of the function to replace the original value.

func swapTwoInts(_ a: inout Int, _ b: inout Int) 

// You call with an & sign (instead of $ sign)

swapTwoInts(&someInt, &anotherInt)

Home that helps.

Thanks @Claude31 feels like you part of swift development team ;) with you on my side I have thousands of great app free in the Apple Store to make people life easier and less busy ;) to spend more time with people and familly ;) hahah thanks man appreciate really

Hi @Claude31 now that I fully understant this code above I believe there Is a big glitch to this solution, because I'm not sure I will need it or not but what if I want to let's say switch all the button back to false ? since the IsSet was parameter by an array but this array is gone even if I use @State for the duration of the tuple finishing I have no way to retrieve the status of the actual button right ? meaning that I do not have a way to give me a full view of the status of all the IsSetbutton in the timekeyinList ? or I may miss something here ?

That's a new question, it would be better to open a new thread.

what if I want to let's say switch all the button back to false ?

all buttons on a row (one project) ? On all rows ?

IsSet was parameter by an array but this array is gone

What do you mean ? Which array is gone ?

Your spec is not clear.

How do you want to switch them all to false ?

  • With a button that would reset them all to false for a given project ?

If so, add a button in TimeKeyInRow ; in the action, you will simple assign all isSet[I] to false.

  • if something else, please explain

But it seems you will have to change the design and have all the buttons status (isSet) for a given project, defined as a property:

struct Project: Identifiable {
    var id: Int = 0// UUID = UUID()
    var name: String
    var shortname: String
    var leftPMtime: Int
    var isTeco: Bool
    var isHourSet : [Bool]

In addition, what is set by isSet in TimeKeyinList ? What is isTeco. That's very confusing by now.

    @State var isSet: [Bool] = [true, false, false, true]

In the present design, it applies to the whole project. What is it for a project ?

If you want to manage isSet for each hour you should store it (as isHourSet) as a property of the project.

To help, you need to very clearly explain what each of those variables refer to.

You will have to restructure a lot.

I let you try this.

If that's what you want, try to understand code and ask for anything unclear. Please do it in a new thread, to avoid ever lasting thread.

Note: that's quick and dirty code, just to have a starting point.

There are clarification questions as comments in code. Please answer.

extension String {
    func paddedToWidth(_ width: Int) -> String {
        let length = self.count
        guard length < width else {
            return self
        }

        let spaces = Array<Character>.init(repeating: " ", count: width - length)
        return self + spaces
    }
}

let allHoursFalse = [false, false, false, false, false, false]     // To simplify code writing, we hard code: there are 6 hours defined

struct TimeKeyinList: View {                     // }, Identifiable {

    @State var projects = [Project] ()        // Now need a State var to be able to modify
    // Not the correct var to keep hours for each project. @State var isSet: [Bool] = [true, false, false, true]
    @State private var hideTeco = false     // What's the purpose of this ? Not used in code
    
    @State var filteredProjects: [Project] = []         /* {  Init will be done in onAppear
        // removeTECO project
        projects.filter { project in
            !project.isTeco
        }
    }*/
    
    var body: some View {
        VStack(spacing: 10) {
            ForEach($filteredProjects, id: \.id) { $project in
                TimeKeyInRow(id: project.shortname, projects: $projects, projectNum: project.projectRow)
            }
        }
        .labelsHidden()
        .onAppear() {   // <<-- Added. All isHourSet initialised as false 
            projects = [
                Project(id: 1, projectRow: 0, name: "Project1, isTeco = false, shortname = MORN, leftPMTime = 131",shortname : "MORN", leftPMtime : 131, isTeco: false, isHourSet: allHoursFalse),
                Project(id: 2, projectRow: 1, name: "Project2, isTeco = false, shortname = MORN, leftPMTime = 122",shortname : "IFF", leftPMtime : 122, isTeco: true, isHourSet: allHoursFalse),
                Project(id: 3, projectRow: 2, name: "Project3, isTeco = false, shortname = MORN, leftPMTime = 133",shortname : "FFI", leftPMtime : 133, isTeco: true, isHourSet: allHoursFalse),
                Project(id: 4, projectRow: 3, name: "Project4, isTeco = false, shortname = MORN, leftPMTime = 444",shortname : "IFFCO", leftPMtime : 444, isTeco: false, isHourSet: allHoursFalse)
            ]
            filteredProjects = projects.filter { project in
                !project.isTeco                // What does isTeco mean ????
            }
//            for (row, project) in filteredProjects.enumerated() {   //     Replaced by array of Bool for hours in each project
//                isSet[row] = !project.isTeco                     // I do not understand what was isSet used for here. 
//            }
        }
    }
}

struct Project: Identifiable {
    var id: Int = 0// UUID = UUID()
    var projectRow: Int // inside projects
    var name: String
    var shortname: String
    var leftPMtime: Int
    var isTeco: Bool
    var isHourSet: [Bool]                              // ### To store for each hour of the project

//    private enum CodingKeys : String, CodingKey {
//        case name, shortname, leftPMtime, isTeco
//    }
}

struct TimeKeyInRow: View,Identifiable {
    var id: String
    @Binding var projects: [Project]     // we pass all projects as well as the projectNum to handle. That eases the update of the project properties
    var projectNum: Int                          // The project displayed in the row
    
    //   @Binding var isSet: Bool          ### No need, it is now a property in project
    let listOfPossibleHours: [Double] = [0.5, 1, 2, 3, 4, 8]
    
    var body: some View {
        HStack {
            Spacer() 
            Text(projects[projectNum].shortname.paddedToWidth(5)) // Text(projects[1].shortname)
                .font(.custom("Menlo", size: 16)) // <<-- To get proper alignment
            Spacer()
            ForEach(Array(listOfPossibleHours.enumerated()), id: \.offset) { (index, hour) in
                HourButton(id: "\(projects[projectNum].shortname)", isSet: $projects[projectNum].isHourSet[index], value: hour)       // HourButton has changed, to pass value of isSet for each hour
            }
            Text("\(projects[projectNum].leftPMtime)")
                .font(.custom("Menlo", size: 16)) // <<-- To get proper alignment
            Spacer()
            // Let's add a button on each row to reset all hours to false for the project. We do it directly in projects array
            Button {
                projects[projectNum].isHourSet = allHoursFalse
            } label: {
                Text("all off")
            }
            Spacer()    // ADDED
        }
    }
}


struct HourButton: View,Identifiable {
    
    var id: String = "hour"
    @Binding var isSet: Bool      // A Binding, as we change the value and want it updated in TimeKeyInRow
    var value: Double = 1
    
    var body: some View {
        HStack {
            Button {
                isSet.toggle()
                print("clic already \(isSet) \(id)h & \(value) has Value")
                
            } label: {
                Label("8h", systemImage: isSet ? "circle.fill" : "circle")
                    .labelStyle(.iconOnly)
                    .foregroundStyle(isSet ? .blue : .gray)
                
            }.id(id)
            
        }
        
    }
}

Thanks I will create a new tread for my new point and answer all your question, ? But I have figure out the above points you mentioned and already started to clean the code even further I which there could be a way for me to share my project to you without every time creating long-lasting thread but I really really apreacite the support you constantly give to me

If you post an email address, we shall be able to share complete project.

Not waiting for this, I have looked again at the code and improved my quick and dirty changes ! No need in fact to pass projects and projectNum, as long as Bindings are correctly defined.

here it is:

extension String {
    func paddedToWidth(_ width: Int) -> String {
        let length = self.count
        guard length < width else {
            return self
        }

        let spaces = Array<Character>.init(repeating: " ", count: width - length)
        return self + spaces
    }
}

let allHoursFalse = [false, false, false, false, false, false]     // To simplify code writing, we hard code: there 6 hours defined

struct TimeKeyinList: View { // }, Identifiable { 

    @State var projects = [Project] ()  // Now need a State var to be able to modify
    // Not the correct var @State var isSet: [Bool] = [true, false, false, true]
    @State private var hideTeco = false
    
    @State var filteredProjects: [Project] = []/
    
    var body: some View {
        VStack(spacing: 10) {
            ForEach($filteredProjects, id: \.id) { $project in
                TimeKeyInRow(id: project.shortname, project: $project)
            }
        }
        .labelsHidden()
        .onAppear() {   // <<-- Added. All hours initialised as falseall
            projects = [
                Project(id: 1, name: "Project1, isTeco = false, shortname = MORN, leftPMTime = 131",shortname : "MORN", leftPMtime : 131, isTeco: false, isHourSet: allHoursFalse),
                Project(id: 2, name: "Project2, isTeco = false, shortname = MORN, leftPMTime = 122",shortname : "IFF", leftPMtime : 122, isTeco: true, isHourSet: allHoursFalse),
                Project(id: 3, name: "Project3, isTeco = false, shortname = MORN, leftPMTime = 133",shortname : "FFI", leftPMtime : 133, isTeco: true, isHourSet: allHoursFalse),
                Project(id: 4, name: "Project4, isTeco = false, shortname = MORN, leftPMTime = 444",shortname : "IFFCO", leftPMtime : 444, isTeco: false, isHourSet: allHoursFalse)
            ]
            filteredProjects = projects.filter { project in
                !project.isTeco
            }
//            for (row, project) in filteredProjects.enumerated() {   //     Replaced by array of Bool for hours in each project
//                isSet[row] = !project.isTeco                        // I do not understand what was isSet used for here.
//            }
        }
    }
}

struct Project: Identifiable {
    var id: Int = 0// UUID = UUID()
    var name: String
    var shortname: String
    var leftPMtime: Int
    var isTeco: Bool
    var isHourSet: [Bool]   // ### To store for each hour of the project
}

struct TimeKeyInRow: View,Identifiable {
    var id: String
    @Binding var project: Project // <<-- Just pass the project itsel  projects: [Project]
    
    // @Binding var isSet: Bool ### No need, it is in project
    let listOfPossibleHours: [Double] = [0.5, 1, 2, 3, 4, 8]
    
    var body: some View {
        HStack {
            Spacer()    // ADDED
            Text(project.shortname.paddedToWidth(5)) // Text(projects[1].shortname)
                .font(.custom("Menlo", size: 16)) // <<-- To get proper alignment
            Spacer()
            ForEach(Array(listOfPossibleHours.enumerated()), id: \.offset) { (index, hour) in
                HourButton(id: "\(project.shortname)", isSet: $project.isHourSet[index], value: hour)        // HourButton has changed, to pas values of isSet for each hour
            }
            Text("\(project.leftPMtime)")
                .font(.custom("Menlo", size: 16)) // <<-- To get proper alignment
            Spacer()
            // Let's add a button on each row to reset all hours to false for the project. We do it directly in projects array
            Button {
                /*projects[projectNum]*/project.isHourSet = allHoursFalse
            } label: {
                Text("all off")
            }
            Spacer()    // ADDED
        }
    }
}

struct HourButton: View,Identifiable {
    
    var id: String = "hour"
    @Binding var isSet: Bool
    var value: Double = 1
    
    var body: some View {
        HStack {
            Button {
                isSet.toggle()
                print("clic already \(isSet) \(id)h & \(value) has Value")
                
            } label: {
                Label("8h", systemImage: isSet ? "circle.fill" : "circle")
                    .labelStyle(.iconOnly)
                    .foregroundStyle(isSet ? .blue : .gray)
                
            }.id(id)
            
        }
        
    }
}

email adress upvote03_ballade at iCloud dot com

explanation of the following code step by step
 
 
Q