WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Project 7 challenge help

Forums > 100 Days of SwiftUI

1. I've added a picker to let the user change the currency (it's a minor quibble) but the currency symbol in the amount text field doesn't change to match. The cell in the content view does change though.

2. Also, I've added a date picker, which works in the addView, but when I try to code it for the contentView, it doesn't work. I have a hunch that it's because I don't have the right data type in the ExpenseItem file.

3. Lastly, I've tried to code the background color to change when the amount is within certain numbers, but I am not sure exactly where to put it. When I put in below the "func removeItems" I get an error that item is not in the scope.

import SwiftUI

struct ContentView: View {
    @StateObject var expenses = Expenses()
    @State private var showingAddExpense = false
    @State private var bgColor = Color.clear

    var body: some View {
        NavigationView {
            List {
                ForEach(expenses.items) { item in
                    HStack {
                        VStack(alignment: .leading) {
                            Text(item.name)
                                .font(.headline)
                            Text(item.type)
                        }
                        Spacer()
                        //VStack {
                           Text(item.amount, format: .currency(code: "\(item.currency)"))
                           // Text(item.date)
                           // }
                     }
                }
                .onDelete(perform: removeItems)
            }
            .navigationTitle("iExpense")
            .toolbar {
                Button {
                    showingAddExpense = true
                } label: {
                    Image(systemName: "plus")
                }
            }
            .sheet(isPresented: $showingAddExpense) {
                AddView(expenses: expenses)
            }

        }

    }
    func removeItems(at offsets: IndexSet) {
        expenses.items.remove(atOffsets: offsets)
    }
//    func changebgColor(at offsets: IndexSet) {
//    If item.amount = 100..299 {
//    bgColor = Color.yellow
//    }
//    If item.amount > 300 {
//    bgColor = Color.red
//    }

}

addView

import SwiftUI

struct AddView: View {
    @ObservedObject var expenses: Expenses
    @Environment(\.dismiss) var dismiss

    @State private var name = ""
    @State private var type = "Personal"
    @State private var amount = 0.0
    @State private var currency = "USD"
    @State private var date = Date()

    let currencies = ["USD", "EUR", "GBP", "JPY"]
    let types = ["Buisness", "Personal"]

    var body: some View {
        NavigationView{
            Form {
                TextField("Item Name", text: $name)

                DatePicker("Date", selection: $date,
            displayedComponents: [.date])

                Picker("Type", selection: $type){
                ForEach(types, id:\.self){
                    Text($0)
                }
            }
                Picker ("Currency", selection: $currency){
                    ForEach(currencies, id:\.self){
                        Text($0)
                    }
                }
            TextField("Amount", value: $amount, format: .currency(code: "\(currency)"))
                .keyboardType(.decimalPad)
        }
        .navigationTitle("Add New Expense")
        .toolbar {
            Button("Save") {
                let item = ExpenseItem(name: name, type: type, amount: amount, currency: currency)//, date: date)
                expenses.items.append(item)
                dismiss()
            }
        }
    }
}
}

ExpenseItem

import Foundation

struct ExpenseItem: Identifiable, Codable {
    var id = UUID()
    let name: String
    let type: String
    let amount: Double
    let currency: String
//    let date:  Int
}

Expenses

import Foundation

class Expenses: ObservableObject {
    @Published var items = [ExpenseItem]() {
        didSet {
            if let encoded = try? JSONEncoder().encode(items) {
                UserDefaults.standard.set(encoded, forKey: "Items")
            }
        }
    }
    init() {
        if let savedItems = UserDefaults.standard.data(forKey: "Items") {
            if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
                items = decodedItems
                return
            }
        }
        items = []
    }
}

   

William asks:

I've tried to code the background color to change when the amount is within certain numbers, but I am not sure exactly where to put it. When I put in below the "func removeItems" I get an error that item is not in the scope.

This is your code:

func changebgColor(at offsets: IndexSet) {   // <--- Why base this on offsets? 
    If item.amount = 100..299 { 
        bgColor = Color.yellow  // <---- where do you apply this bgColor?
    }
    If item.amount > 300 {
        bgColor = Color.red
    }

This looks (to me!) that you have a background in traditional, procedural programming. Perhaps C#, Java, Python, etc.

IF something happens, I want THIS to happen.

SwiftUI is a declarative language. Simply DECLARE how the view should look. Make it so!

Here's how you declare the background color of a Text view.

    Text("Home View")
        .foregroundColor(.cyan)
        .background { Color.red } // <-- Declare the background color! It's always red

How do you declare different colors for different circumstances? This uses the ternary operator to determine which color to use.

    var isOverOneHundredDollars: Bool { itemAmount > 100 }  // Computed property. It's true or false.
    Text("Home View")
        .foregroundColor(.cyan)
        .background { isOverOneHundredDollars ? Color.red : Color.blue } // <-- Declare the background color!

Maybe you want to try another method?

// Declare the background color based on certain conditions.
    var backgroundColor: Color { if itemAmount > 100 { return Color.red } else {return Color.blue }  }

     Text("Home View")
          .foregroundColor(.cyan)
          .background { backgroundColor } // <-- Use the computed property

   

I put your code

var isOverOneHundredDollars: Bool { itemAmount > 100 }
    Text("Home View")
        .foregroundColor(.cyan)
        .background { isOverOneHundredDollars ? Color.red : Color.blue }

and

var backgroundColor: Color { if itemAmount > 100 { return Color.red } else {return Color.blue }  }

     Text("Home View")
          .foregroundColor(.cyan)
          .background { backgroundColor }

right after

Text(item.amount, format: .currency(code: "\(item.currency)"))

But I get the following error for either "Cannot declare local computed variable in result builder". Not sure if I am inserting it in the wrong place? or if there is something else I have to do before it will work

   

William wonders:

Error: "Cannot declare local computed variable in result builder". Not sure if I am inserting it in the wrong place? or if there is something else I have to do before it will work

You may need to start over with Day 1 and study the structure of View structs. This is fundamental and important you know this cold before tying to move on.

Think of a struct as a cardboard box full of variables. Some variables you'll pass in, other variables you'll compute. Still others will be populated by users via Pickers, Buttons, and TextFields. And you may also populate variables by fetching data from data stores, files, or the internet.

In short, your struct is a box with variables. Later, you'll add functions along side the variables.

If you decide that one of your structs is going to actually show something on a screen, fields, data, graphics, buttons, etc, then this struct MUST conform to the View protocol.

The single requirement of the View protocol is that your struct MUST have a computed var named body. What ever this computed var does, it must create some kind of View. You can have many other vars, but you must have one named body.

// think of this struct as a box containing a bunch of variables.
struct ConcertPerformancesView: View {
    var performances: [Performance]   //  <-- This is a variable you must set.

    var numberOfConcerts: Int { performances.count } // This is a computed variable.

    // body is a computed variable. Note: it builds some sort of view.
    var body: some View {
        // lots of code here to show the dates for upcoming concerts
        // lots of stuff happens here, but the end result is some kind of view.
    }
}

The error message you received is clear.

Error: "Cannot declare local computed variable in result builder".

This is saying you cannot declare a computed variable INSIDE a result builder. Take a look at my example. Which of those vars is a result builder? Spoiler alert: It's the body var.

Consequently you cannot do this:

var body: some View {
        // This line is NOT ALLOWED
        var backgroundColor: Color { if itemAmount > 100 { return Color.red } else {return Color.blue }  }
        ... snip ....
    }

So given what I said about structs containing a bunch of vars, where does the backgroundColor var definition belong?

   

So since it can't be in the body, var backgroundColor: Color { if item.amount > 100 { return Color.red } else {return Color.blue } } needs to go after it. I am getting an error that "Cannot find 'item' in scope". I know that I declared it in "ForEach(expenses.items) { item in ...". And that since I declared it inside the body, that it probably doesn't commute over outside. How/where do I declare it so that it will be in the scope?

   

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.