4

I have two text fields that change the value of two @State variables, namely startingMileage and endingMileage, and a stepper that changes the value of a third @State variable named fuelAdded. I'm trying to use the user's inputs and do a calculation to calculate miles per gallon. In playground, my code works as expected. It does not work in a SwiftUI project, however.

Tried running the code in playground as below:

func CalcMPG(start: String, end: String, fuel: Double) -> Int {
    let start = Int(start) ?? 1
    let end = Int(end) ?? 1
    let fuel = Int(fuel)
    let mpg = (end-start) / fuel
    return mpg
}


var endingMileage:String = "9250"
var startingMileage:String = "9000"
var fuelAdded:Double = 20

let milesPerGallon = CalcMPG(start: startingMileage, end: endingMileage, fuel: fuelAdded)
print("Fuel Efficiency: \(milesPerGallon) mpg")

This works as expected.

struct ContentView : View {

    @State var startingMileage: String = ""
    @State var endingMileage: String = ""
    @State var fuelAdded: Double = 10
    @State var carModel: String = ""
    @State var showMPGInfo = false
    @State var milesPerGallon: Int = 10

    func CalcMPG(start: String, end: String, fuel: Double) -> Int {
        let start = Int(start) ?? 1
        let end = Int(end) ?? 1
        let fuel = Int(fuel)
        let mpg = (end-start) / fuel
        return mpg
    }


    var body: some View {
        NavigationView {
            VStack{
                HStack {
                    Text("Car Model:")
                    Spacer()
                    TextField($carModel, placeholder: Text("Toyota Corolla"))
                        .textFieldStyle(.roundedBorder)
                }
                HStack {
                    Text("Starting ODO:")
                    Spacer()
                    TextField($startingMileage, placeholder: Text("8000"))
                    .textFieldStyle(.roundedBorder)
                    Text("miles")
                }

                HStack {
                    Text("Ending ODO:")
                    Spacer()
                    TextField($endingMileage, placeholder: Text("9000"))
                        .textFieldStyle(.roundedBorder)
                    Text("miles")
                }
                HStack {
                    Stepper(value: $fuelAdded, in: 0...20, step: 0.5) {
                        Text("Fuel Added: \(fuelAdded, specifier: "%0.1f") gallons")
                    }
                }
                Button(action: {
                    self.showMPGInfo.toggle() }){
                        Text("Show/Hide MPG")
                }
                    if showMPGInfo {
                        Spacer()
                        milesPerGallon = CalcMPG(start: startingMileage, end: endingMileage, fuel: fuelAdded)
                        Text("Fuel effiency: \(milesPerGallon) MPG")
                            .font(.largeTitle)
                    }
                }.padding()
                .navigationBarTitle(Text("Gas Mileage Calculator"))
        }
    }
}

When the user clicks on "Show/Hide MPG". I expect the end resulting text to be "Fuel Efficiency: xx MPG"

However, I get the following errors:

ContentView.swift:34:19: Unable to infer complex closure return type; add explicit type to disambiguate

It doesn't appear anywhere sensical to me... It highlights the starting VStack in the NavigationView.

Any thoughts?

mg_berk
  • 731
  • 2
  • 8
  • 12
  • Okay, it must be something to do with the declaration of: `milesPerGallon = CalcMPG(...) ` because as soon as I take that out and just put \(CalcMPG(...)) into the text I want to return it works as desired. Anyone know what I'm doing wrong here? Is it not right to use a @State var in this case? – mg_berk Jun 16 '19 at 05:41

1 Answers1

3

The problem is that the assignment to milesPerGallon does not work well with the “function builder syntax” used to build the arguments of the view hierarchy. It becomes a bit more obvious if we replace @State var milesPerGallon by a local variable (which is is, it does not carry state on which the view depends, only a intermediate value):

if showMPGInfo {
    Spacer()
    let milesPerGallon = CalcMPG(start: startingMileage,
                                 end: endingMileage, fuel: fuelAdded)
    Text("Fuel effiency: \(milesPerGallon) MPG")
        .font(.largeTitle)
}

Now the compiler error is

Closure containing a declaration cannot be used with function builder 'ViewBuilder'

For more information about the function builder syntax see What enables SwiftUI's DSL? (which has also links to the documentation).

The simplest solution would be to avoid a local variable and interpolate the text directly:

if showMPGInfo {
    Spacer()
    Text("Fuel effiency: \(CalcMPG(start: startingMileage, end: endingMileage, fuel: fuelAdded)) MPG")
        .font(.largeTitle)
}

Other solutions are to calculate the text field in an “immediately evaluated closure”:

if showMPGInfo {
    Spacer();
    { () -> Text in
        let milesPerGallon = CalcMPG(start: startingMileage,
                                      end: endingMileage,
                                      fuel: fuelAdded)
        return Text("Fuel effiency: \(milesPerGallon) MPG")
    }()
    .font(.largeTitle)
}

or to define an auxiliary function

func resultField(start: String, end: String, fuel: Double) -> Text {
    let  milesPerGallon = CalcMPG(start: startingMileage, end: endingMileage, fuel: fuelAdded)
    return Text("Fuel effiency: \(milesPerGallon) MPG")
}

and use it as

if showMPGInfo {
    Spacer()
    resultField(start: startingMileage, end: endingMileage,
                fuel: fuelAdded)
        .font(.largeTitle)
}

There may be other workarounds, but that is what I came up with so far.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382