I am brand new to Swift (and coding in general). I am working on an app that will output a calculation based off the tracking of two states. The two states are brewModel and waterAmount. I am able to successfully create a function that will return one calculation based on the two states. However, now I am trying to create a Picker that will toggle the calculation between two measurements - grams and tablespoons. This is where I am having trouble.
I tried to write a series of conditionals in different ways such as if and else if as well as switch cases, but it doesn't work. When I build the simulator, Xcode will just think for a long time until I stop it. Sometimes I get error messages after I manually stop it and sometimes I don't. Today I got "Command CompileSwiftSources failed with a nonzero exit code."
Does anyone have any suggestions?
Also, I apologize if my code is messy, I have a bunch of things commented out that I am playing with. The func computeGrinds does work but just for the one calculation. Thank you!
import SwiftUI
struct Water: View {
// @EnvironmentObject var favorites: Favorites
@State var animationInProgress = true
@State var brewModel: BrewModel
@State var waterAmount: Int = 1
@State var grindsSelection = "tbsp"
var grindOptions = ["tbsp", "grams"]
// var resultGrindCalc: Double {
//
// var value = Double(0)
// }
// switch grindsSelection {
// case "tbsp" || brewModel.frenchPress:
// value = Double(waterAmount) * 2.5
//
// }
//
// func computeGrinds () -> Double {
// switch brewModel {
// case .frenchPress, .chemex:
// return (2.5 * Double(waterAmount))
// case .drip :
// return Double(2 * Double(waterAmount))
// case .mokaPot:
// return Double(1 * Double(waterAmount))
// case .aeroPress:
// return Double(1.6 * Double(waterAmount))
// // default:
// // return(1 * Double(waterAmount))
// }
// }
var body: some View {
VStack (spacing: 5) {
Spacer()
HStack {
// Text("").padding(20)
Text("How many cups do you want to brew?")
Picker("", selection: $waterAmount) {
ForEach(1...15, id: \.self){
Text("\($0)")
}
}
// Spacer()
}.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
// gif/image conditionals
if (brewModel == .frenchPress) {
LottieView(name: "frenchpress", loopMode: .loop)
} else if brewModel == .chemex {
LottieView(name: "pourover", loopMode: .loop)
} else if brewModel == .aeroPress {
LottieView(name: "aeropress", loopMode: .loop)
} else if brewModel == .mokaPot {
LottieView(name: "mokapot", loopMode: .loop)
} else if brewModel == .drip {
Image("Drip")
.resizable()
.scaledToFit()
}
// I would have more conditionals but testing with just these two for now
var testingCalcCond = Double
if (brewModel == .frenchPress)||(grindsSelection=="tbsp") {
testingCalcCond = (2.5 * Double(waterAmount))
} else if (brewModel == .frenchPress)||(grindsSelection=="grams") {
testingCalcCond = (16 * Double(waterAmount))
}
let formatted = String(format: "%.2f", testingCalcCond)
// let formatted = String(format: "%.2f", computeGrinds())
HStack {
Text("**\(formatted)**")
Picker("Select Grinds Units: ", selection: $grindsSelection, content: {
ForEach(grindOptions, id: \.self) {
Text($0)
}
}).onChange(of: grindsSelection) { _ in computeGrinds() }
Text("of coffee grinds needed")
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
}
Spacer()
}
}
struct Water_Previews: PreviewProvider {
static var previews: some View {
Water(brewModel: .drip)
}
}
}
*I'm using Xcode 13.2.1 *I'm using swiftUI
CodePudding user response:
There are 2 aspects you want to think over:
1. How to update the result value based on the inputs.
Your result value is based on two inputs: brewModel and waterAmount. Both are @State vars and changed by a picker.
I changed your computeGrinds func to a computed property, because this will be automatically called when one of the two base values changes. Then there is no need for .onchange anymore, you can just use the var value – it will always be up to date.
2. recalculating from tbsp to grams.
This is more of a math thing: As I understand, for .frenchPress you need either 2.5 tbsp – or 16 grams per cup. So 1 tbsp = 16 / 2.5 = 6.4 grams. Once you know that you just have to go through the switch case once, and use the unitValue to recalculate. I integrated that too ;)
Here is my simplified code:
enum BrewModel {
case frenchPress
case chemex
case drip
case mokaPot
case aeroPress
}
struct ContentView: View {
@State var animationInProgress = true
@State var brewModel: BrewModel = .frenchPress
@State var waterAmount: Int = 1
@State var grindsSelection = "tbsp"
let grindOptions = ["tbsp", "grams"]
// computed var instead of func, does the same
var computeGrinds: Double {
// transforms tbsp = 1 to grams (= 6.4 ?)
var unitValue: Double = 1.0
if grindsSelection == "grams" {
unitValue = 6.4
}
switch brewModel {
case .frenchPress, .chemex:
return (2.5 * unitValue * Double(waterAmount))
case .drip :
return Double(2 * unitValue * Double(waterAmount))
case .mokaPot:
return Double(1 * unitValue * Double(waterAmount))
case .aeroPress:
return Double(1.6 * unitValue * Double(waterAmount))
}
}
var body: some View {
VStack (spacing: 5) {
HStack {
Text("How many cups do you want to brew?")
Picker("", selection: $waterAmount) {
ForEach(1...15, id: \.self){
Text("\($0)")
}
}
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color.brown, lineWidth: 8)
)
.padding(.bottom)
let formatted = String(format: "%.2f", computeGrinds)
HStack {
Text("**\(formatted)**")
Picker("Select Grinds Units: ", selection: $grindsSelection, content: {
ForEach(grindOptions, id: \.self) {
Text($0)
}
})
Text("of coffee grinds needed")
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color.brown, lineWidth: 8)
)
}
}
}
CodePudding user response:
I would suggest getting very familiar with Measurement
you are recreating some work that is automatically done for you.
And even more important it will provide the math for you.
2.5 tablespoon = 37.5 grams not 16, not sure if this is a math mistake or something intentional.
If you have 1 cup of French press you would add 2.5 tablespoons or 37.5 grams of coffee.
or
If you have 1 cup of French press you would add 1.08 tablespoons or 16 grams of coffee.
Just change the multiplier
to the correct version.
Also, put the information for each type in the model
struct BrewModel{
var imageName: String
//Per cup multiplier
var multiplier: Measurement<UnitMass>
///Used to calculate
func needed(water: Measurement<UnitVolume>) -> Measurement<UnitMass>{
//Do your math here, just a sample
let cupsWater = water.converted(to: .cups).value
let brew = multiplier.converted(to: .grams).value
let value = cupsWater * brew
return Measurement(value: value, unit: .grams)
}
static let frenchPress = BrewModel(imageName: "frenchpress", multiplier: Measurement(value: 2.5, unit: .tablespoons))
static let drip = BrewModel(imageName: "Drip", multiplier: Measurement(value: 2, unit: .tablespoons))
static let mokaPot = BrewModel(imageName: "mokaPot", multiplier: Measurement(value: 1, unit: .grams))
static let aeroPress = BrewModel(imageName: "aeropress", multiplier: Measurement(value: 1.6, unit: .grams))
static let chemex = BrewModel(imageName: "pourover", multiplier: Measurement(value: 1, unit: .grams))
}
Then your View
will update automatically with some simple changes.
struct WaterView: View {
@State var animationInProgress = true
@State var brewModel: BrewModel
//Use measurement
@State var waterAmount: Measurement<UnitVolume> = Measurement(value: 1, unit: .cups)
//use units instead of strings
@State var grindsSelection: UnitMass = .tablespoons
//use units instead of strings
var grindOptions: [UnitMass] = [.grams, .tablespoons]
var body: some View {
VStack (spacing: 5) {
Spacer()
HStack {
Text("How many cups do you want to brew?")
Picker("", selection: $waterAmount) {
ForEach(1...15, id: \.self){
Text("\($0)").tag(Measurement(value: Double($0), unit: UnitVolume.cups))
}
}
}.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
//Call the provided variable
Image(brewModel.imageName)
.resizable()
.scaledToFit()
//Calculate to picker units
let formatted = String(format: "%.2f", brewModel.needed(water: waterAmount).converted(to: grindsSelection).value)
HStack {
Text("**\(formatted)**")
Picker("Select Grinds Units: ", selection: $grindsSelection, content: {
ForEach(grindOptions, id: \.self) {
Text($0.symbol)
}
})
Text("of coffee grinds needed")
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
}
Spacer()
}
}
struct WaterView_Previews: PreviewProvider {
static var previews: some View {
WaterView(brewModel: .frenchPress)
}
}
You'll have to add tablespoons to UnitMass
extension UnitMass{
static let tablespoons = UnitMass(symbol: "tbsp", converter: UnitConverterLinear(coefficient: 0.015))
}
My using measurement you can support any units with really minor changes like ounces, liters, gallons of water. Since needed
changes the water to cups to do the calculation that incoming measurement can be anything.