Loops with conditions

Hello!

I am trying to build out a finance app as some practice. I want to display a list of transactions but only those relevant to a certain payment category. I have a ForEach loop (below) to go through all payments (the single row is built on another screen CategoryRow) but not sure how to create a condition so it only displays the category that was selected to view payments for.

I have a data model that has paymentCategory.categoryName

ForEach(model.payments) category in
CategoryRow(payment: category)

Thanks!

@LloydH

Hi Lloyd,

Welcome to the community.

Ideally we need to see more code related to what your ForEach is inside of.

If you have your ForEach() loop inside a List { } then when you tap on a row the category for that row is selected and depending on how you are presenting some sort of DetailView for the payments related to that category you should be able to see the payments related to it.

We really need to see more code so that we can point you in the right direction.

Hi Chris. Thanks!

So currently I just have it in a ScrollView. The ForEach loop then just runs through the full list of payments which are stored in a JSON file.
The ForEach uses the template of a single row of a payment in another view CategoryRow.

struct CategoryPaymentsView: View {
    
    @EnvironmentObject var model: PaymentModel
    var payment:Payment

    var body: some View {
            
            ScrollView {  
                
                ForEach(model.payments) { category in
                        CategoryRow(payment: category)
                     
                        }  
                    }

But instead of displaying all payments as this code does, I just want it to display payments associated with a certain Category. Where the model currently looks like the below. I was thinking there would be a condition I could add but was not sure!

struct Payment: Identifiable, Decodable {
    
    var id:UUID?
    var paymentName:String
    var paymentAmount:Double
    var paymentMonth:String
    var paymentYear:Int
    var paymentDay:Int
    var paymentCurrency:String
    let paymentMerchant:String?
    var paymentCategory:Category
    var paymentPlan:Plan
}

struct Category: Identifiable, Decodable {
    
    var id:UUID?
    var categoryName:String?
    var categoryColor:String?
    var categoryIcon:String?
    
}

Oh sorry realised what you mean on the DetailView. So currently have a Navigation Link on this page, where I’m referencing the payment. Then select that Image & it takes you to the full list where I have the ForEach

struct ViewPaymentAnalytics: View {
    
    var payment:Payment
    
    var body: some View {

NavigationLink(destination: CategoryPaymentsView(payment: payment),
                                label: {
                            
                            Image(systemName: "chevron.right.circle.fill")
                                .resizable()
                                .frame(width: 30, height: 30)
                                .foregroundColor(Color("Primary"))
                            
                        })

@LloydH

Hi Lloyd,

It would be helpful if you supplied the entire code for each of the Views rather then selective snippets so we can see what works and what does not and then we are in a better position to advise the best way forward. Just snips of code here and there don’t paint the entire picture and is not possible to do any testing.

Hey,

Sorry I thought it was all included - let me try again!

Essentially I have one page here - ViewPaymentAnalytics - that has a NavigationLink to another page CategoryPayments View.

struct ViewPaymentAnalytics: View {
    
    var payment:Payment
    
    var body: some View {
        
        VStack (alignment: .leading) {
            
            //header
            Text("PAYMENT ANALYTICS")
                .font(.system(size: 14, weight: .semibold))

                    //See all category payment
                    HStack {
                        Image(systemName: "square.grid.2x2")
                            .resizable()
                            .frame(width: 30, height: 30)
                        VStack (alignment: .leading) {
                            Text("£15 out of £100 for this category")
                                .font(.system(size: 14, weight: .bold))
                            Text("See all category payments in this Plan ")
                                .font(.system(size: 12))
                            
                        }
                        
                        Spacer()
                        NavigationLink(destination: CategoryPaymentsView(payment: payment),
                                label: {
                            
                            Image(systemName: "chevron.right.circle.fill")
                                .resizable()
                                .frame(width: 30, height: 30)
                                .foregroundColor(Color("Primary"))
                            
                        })
                        
                    }
                }
                
            }
            .padding()
            .background(RoundedRectangle(cornerRadius: 10).stroke(Color("SecondaryText"), lineWidth: 1))
            .background(Color.white)
        }
        
    }
}

Clicking on the NavigationLink takes to the CategoryPaymentsView

struct CategoryPaymentsView: View {
    
    @EnvironmentObject var model: PaymentModel
    var payment:Payment
    
    var body: some View {

        VStack (spacing: 20) {
            
            ScrollView {
                            //For each loop for the sections
                
                ForEach(model.payments) { category in

                        CategoryRow(payment: category)
                        
                        }

                        
                    }
                }
        .padding(.horizontal)
                            }
                            
                        }

And this is made up of a loop of a CategoryRow

struct CategoryRow: View {
    
    var payment:Payment
    
    var body: some View {
        
        HStack {
            //Icon
            ZStack {
                Circle()
                    .frame(width: 40, height: 40)
                    .foregroundColor(Color("\(payment.paymentCategory.categoryColor!)"))
                Image(systemName: "\(payment.paymentCategory.categoryIcon!)")
                    .resizable()
                    .frame(width: 20, height: 20)
                    .foregroundColor(.white)
            }
            
            VStack (alignment: .leading) {
                //Payment name
                Text(payment.paymentName)
                    .font(.system(size: 21, weight: .medium))
                
            }
            
            Spacer()
            //Payment amount amended to currency format
            Text(payment.paymentAmount, format: .currency(code: payment.paymentCurrency))
                .font(.system(size: 21, weight: .bold))
        }
        .accentColor(.black)


    }
}

Thank you

@LloydH

Hi Lloyd,

Some sample data would help. Have you got any?

Maybe share your json file and the code you have to load it into the payments array. I’m struggling to make sense of what is going on without some data.

Yes of course. I created a json myself here is a sample of it as it’s quite long

{
        "paymentName": "After work",
        "paymentAmount": 20.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 20,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Activities",

            "categoryColor": "ActivitiesIcon",

            "categoryIcon": "camera"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    }

And the ViewModel I have created

class PaymentModel: ObservableObject {
    
    @Published var payments = [Payment]()
    @Published var categories = Category()
    @Published var plans = Plan()

    init() {
        
        let pathString = Bundle.main.path(forResource: "payments", ofType: "json")
        
        if pathString != nil {
            
            let url = URL(fileURLWithPath: pathString!)
            
            do {
                let data = try Data(contentsOf: url)
                
                let decoder = JSONDecoder()
                
                do {
                    var jsonPayments = try decoder.decode([Payment].self, from: data)
                    
                    
                    for index in 0..<jsonPayments.count {
                        jsonPayments[index].id = UUID()
                    }
                    
                    self.payments = jsonPayments
                }
                catch {
                    print("Couldn't parse Payments")
                }
                
            }
            catch {
                print("Couldn't create Data object")
            }
            
        }
    }

@LloydH

Hi Lloyd,

If you are not keen on sharing the entire json file publicly then can you share it with me via a private message? To do so, reply to the message I sent you.

I really need more than just one instance of a payment so that I have various categories since that was part of the initial question where you said that you wanted to be able to pick a specific category. It does not matter how big the json file is.

Sorry no it’s fine was just trying to be helpful. It’s just random data I created & copy & pasted it over.

[{
        "paymentName": "After work",
        "paymentAmount": 20.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 20,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Activities",

            "categoryColor": "ActivitiesIcon",

            "categoryIcon": "camera"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },

    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },

    {
        "paymentName": "After work",
        "paymentAmount": 20.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 20,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Activities",

            "categoryColor": "ActivitiesIcon",

            "categoryIcon": "camera"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },

    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }
    },
 {
        "paymentName": "After work",
        "paymentAmount": 20.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 20,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Activities",

            "categoryColor": "ActivitiesIcon",

            "categoryIcon": "camera"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }
    },

    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },
    
    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },
    
    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },
    
    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },
    
    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },
    
    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },
    
    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    },
    
    {
        "paymentName": "Bus",
        "paymentAmount": 10.00,
        "paymentMonth": "March",
        "paymentYear": 2023,
        "paymentDay": 10,
        "paymentCurrency": "GBP",
        "paymentMerchant": "Stripe",
        "paymentCategory": {
            "categoryName": "Transport",

            "categoryColor": "TransportIcon",

            "categoryIcon": "bus"
        },
        "paymentPlan": {
            "planName": "Colombia",

            "planMonth": "August",

            "planYear": 2023,

            "planDay": 2
        }

    }


]

Ok thanks. It’s late where I am and I need some :zzz::zzz::zzz::zzz::zzz: so I’ll get back to you tomorrow morning or after work.

Cheers.

1 Like

@LloydH

G’Day Lloyd,

Of the Views that you have provided, I was trying to figure out what the order because in each case you have a var payment: Payment.

Anyway, I noodled around for a while and got something to work where I extracted a list of unique categories from the data and listed those in ViewPaymentAnalytics to select from and then depending on the category selected, the CategoryPaymentsView showed only those records.

Here are the updated Views. Note that I have commented out some of the code in ViewPaymentAnalytics just to simplify the display for the purposes of getting it to work.

struct ViewPaymentAnalytics: View {
    @EnvironmentObject var model: PaymentModel

    @State private var categories = [String]()

    var body: some View {

        NavigationStack {
            VStack (alignment: .leading) {

                //header
                Text("PAYMENT ANALYTICS")
                    .font(.system(size: 14, weight: .semibold))

                //See all category payment
//                HStack {
//                    Image(systemName: "square.grid.2x2")
//                        .resizable()
//                        .frame(width: 30, height: 30)
//                    VStack (alignment: .leading) {
//                        Text("£15 out of £100 for this category")
//                            .font(.system(size: 14, weight: .bold))
//                        Text("See all category payments in this Plan ")
//                            .font(.system(size: 12))
//
//                    }

                    Spacer()
                    List(categories, id: \.self) { category in
                        NavigationLink(destination: CategoryPaymentsView(category: category)) {
                            Text(category)
                        }
                        .padding()
                        .background(RoundedRectangle(cornerRadius: 10).stroke(Color.secondary, lineWidth: 1))
                        .background(Color.white)
                    }
                    .listStyle(.plain)
//                    NavigationLink(destination: CategoryPaymentsView(),
//                                   label: {
//
//                        Image(systemName: "chevron.right.circle.fill")
//                            .resizable()
//                            .frame(width: 30, height: 30)
//                            .foregroundColor(Color.primary)
//
//                    })

//                }
            }

        }
        .padding(.horizontal, 5)
        .onAppear {
            getCategories()
        }

    }

    func getCategories() {
        let mappedCategories = model.payments.map { $0.paymentCategory.categoryName! }
        let categoriesSet = Set(mappedCategories)
        categories = Array(categoriesSet)
    }
}


struct ViewPaymentAnalytics_Previews: PreviewProvider {
    static var previews: some View {
        ViewPaymentAnalytics()
            .environmentObject(PaymentModel())
    }
}
struct CategoryPaymentsView: View {
    
    @EnvironmentObject var model: PaymentModel
    var category: String
    
    var body: some View {
        
   
            VStack {
                List {
                    //For each loop for the sections

                    ForEach(model.payments) { payment in
                        if payment.paymentCategory.categoryName == category {
                            CategoryRow(payment: payment)
                        }

                    }
                }
                .listStyle(.plain)
            }



    }
    
}

struct CategoryPaymentsView_Previews: PreviewProvider {
    static var previews: some View {
        CategoryPaymentsView(category: "Activities")
            .environmentObject(PaymentModel())
    }
}
struct CategoryRow: View {

    var payment:Payment

    var body: some View {

        HStack {
            //Icon
            ZStack {
                Circle()
                    .frame(width: 40, height: 40)
                    .foregroundColor(Color("\(payment.paymentCategory.categoryColor!)"))
                Image(systemName: payment.paymentCategory.categoryIcon!)
                    .resizable()
                    .frame(width: 20, height: 20)
                    .foregroundColor(.white)
            }

            VStack (alignment: .leading) {
                //Payment name
                Text(payment.paymentName)
                    .font(.system(size: 21, weight: .medium))

            }

            Spacer()
            //Payment amount amended to currency format
            Text(payment.paymentAmount, format: .currency(code: payment.paymentCurrency))
                .font(.system(size: 21, weight: .bold))
        }
        .padding(.horizontal)
        .accentColor(.black)


    }
}

struct CategoryRow_Previews: PreviewProvider {
    static var previews: some View {
        CategoryRow(payment: Payment(id: UUID(), paymentName: "Bus", paymentAmount: 10.00, paymentMonth: "March", paymentYear: 2023, paymentDay: 10, paymentCurrency: "GBP", paymentMerchant: "Stripe", paymentCategory: Category(id: UUID(), categoryName: "Category Name", categoryColor: "TransportIcon", categoryIcon: "bus")))
    }
}

Hey Chris,

Thank you so much, I’ve played with it this morning & able to get it to work. Appreciate you taking so much time on it!

I think in the future I’d like it to go from one button/image (rather than list) to the filtered view you created but not sure if it’s possible / I will use what you have provided to give it a go :slight_smile:

Thanks again!

1 Like