skip to Main Content

I have an app with an array of models that contain number fields. I’d like to be
able to sum any given data field. I can do so with map by using the data field
name ($0.cups for example). However I would like to programmatically send in a
variable for "cups" and nothing I have tried works.

Here’s some example code. I’d like to pass the field name that I want to total into
the function sumOnAttribute

struct ContentView: View {

    @State private var answer: String = ""

    @State private var startingModels = [
        StartingModel(name: "abe", age: 1, bags: 2, cups: 3),
        StartingModel(name: "abe", age: 11, bags: 12, cups: 13),
        StartingModel(name: "abe", age: 21, bags: 22, cups: 23),
        StartingModel(name: "donna", age: 31, bags: 32, cups: 33),
        StartingModel(name: "elsie", age: 41, bags: 42, cups: 43),
        StartingModel(name: "farah", age: 51, bags: 52, cups: 53)
    ]//some data

    var body: some View {
    
        VStack(spacing: 10) {
            Text("Do Some Math")
                .font(.title)
            Text(answer)
                .font(.headline)
            Button {
                answer = String(sumOnAttribute(filterField: "abe", sumField: "cups"))
            } label: {
                Text("Make it So")
            }
            .font(.system(size: 20))
        }//v
    }//body

    func sumOnAttribute(filterField: String, sumField: String) -> Double {
    
        let filteredAttribute = startingModels.filter({ $0.name == filterField })
    
        //$0.sumField does not work, $0.value(sumField) does not work
        let nums = filteredAttribute.map({ $0.cups })

        let total = nums.reduce(0, +)
        return total
    }
}//struct

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct StartingModel: Identifiable {
    let id = UUID()
    let name: String
    let age: Double
    let bags: Double
    let cups: Double
}//model

Seems like this should be simple, but I have not found any answer in a lot of searches.
Any guidance would be appreciated. Xcode 13.3, iOS 15.4

2

Answers


  1. One option is a KeyPath — it’s not a String like you’re using, but rather a compile-time-safe path to the property you’re looking for. An implementation might look like this:

    struct ContentView: View {
    
        @State private var answer: String = ""
    
        @State private var startingModels = [
            StartingModel(name: "abe", age: 1, bags: 2, cups: 3),
            StartingModel(name: "abe", age: 11, bags: 12, cups: 13),
            StartingModel(name: "abe", age: 21, bags: 22, cups: 23),
            StartingModel(name: "donna", age: 31, bags: 32, cups: 33),
            StartingModel(name: "elsie", age: 41, bags: 42, cups: 43),
            StartingModel(name: "farah", age: 51, bags: 52, cups: 53)
        ]//some data
    
        var body: some View {
        
            VStack(spacing: 10) {
                Text("Do Some Math")
                    .font(.title)
                Text(answer)
                    .font(.headline)
                Button {
                    answer = String(sumOnAttribute(filterField: "abe", sumField: .cups))
                } label: {
                    Text("Make it So")
                }
                .font(.system(size: 20))
            }//v
        }//body
    
        func sumOnAttribute(filterField: String, sumField: KeyPath<StartingModel,Double>) -> Double {
            let filteredAttribute = startingModels.filter({ $0.name == filterField })
            return filteredAttribute.map({ $0[keyPath:sumField] }).reduce(0, +)
        }
    }//struct
    

    If you really wanted to use a String, you could consider using @dynamicMemberLookup: https://www.hackingwithswift.com/articles/55/how-to-use-dynamic-member-lookup-in-swift

    Login or Signup to reply.
  2. not clear what you want, maybe you could try something like this, "…to pass the field name that I want to total into the function sumOnAttribute…":

    struct ContentView: View {
        @State private var namefield: String = ""  // <-- here
        @State private var sumfield: String = ""   // <-- here
        @State private var answer: String = ""
        
        @State private var startingModels = [
            StartingModel(name: "abe", age: 1, bags: 2, cups: 3),
            StartingModel(name: "abe", age: 11, bags: 12, cups: 13),
            StartingModel(name: "abe", age: 21, bags: 22, cups: 23),
            StartingModel(name: "donna", age: 31, bags: 32, cups: 33),
            StartingModel(name: "elsie", age: 41, bags: 42, cups: 43),
            StartingModel(name: "farah", age: 51, bags: 52, cups: 53)
        ]
        
        var body: some View {
            
            VStack(spacing: 10) {
                TextField("name field", text: $namefield).border(.red) // <-- here
                TextField("sum field", text: $sumfield).border(.red)   // <-- here
                Text("Do Some Math")
                    .font(.title)
                Text(answer)
                    .font(.headline)
                Button {
                    answer = String(sumOnAttribute(filterField: namefield, sumField: sumfield)) // <-- here
                } label: {
                    Text("Make it So")
                }
                .font(.system(size: 20))
            }
        }
        
        func sumOnAttribute(filterField: String, sumField: String) -> Double {
            let filteredAttribute = startingModels.filter{ $0.name == filterField }
            // -- here
            var nums: [Double] = []
            switch sumField {
              case "cups": nums = filteredAttribute.map{ $0.cups }
              case "bags": nums = filteredAttribute.map{ $0.bags }
              case "age": nums = filteredAttribute.map{ $0.age }
              default: break
            }
            return nums.reduce(0, +)
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search