0

I am still totally new to Swift / SwiftUI and coding in general. I asked this question before but it was closed because it was apparently a duplicate, but (!) this does not account for SwiftUI and I cannot get this thing to work in SwiftUI, because even if I use the suggested NSExpression, I cannot use the result, because it is not a View and I have no idea on how to convert it to a View (see the update at bottom of the question).

I am trying to make a simple converter with a set of predefined conversion from a DataModel. So far I can populate the ContentView and move into a DetailView where I have created a slider which automatically updates a TextView whenever the slider is moved and the whole screen is populated through the DataModel (I hope I explain this the right way).

Now my problem is that every single conversion uses a different calculation obviously. For instance "/ 1000" or "* 50". I have stored these parts as Strings in the DataModel. Now what I'd like to do is to use these Strings in my calculation, but I have no idea how I can include them. Obviously I cannot convert these to Doubles to make a calculation. Here is the code and DataModel:

DataModel:

struct Converter: Identifiable {
    var id = UUID()
    var title: String
    var range = 0.0...0.0
    var calc: String
    var sliderValue: Double

    var color: [Color]
    var description: String
}

As you can see "calc" is of type String. I am also using "range" which I defined with 0.0...0.0 because I didn't know how I could initialize it otherwise (I always got an error because of a closed range (?) so this is what I cam up with to silence the error.

Here is an example of some data:

let converterData: [Converter] = [
    Converter(
        title: "Conversion1",
        range: 0.0...200,
        calc: "/ 1000",
        sliderValue: 0.0,
        color: [Color.blue, Color.red],
        description: "This is conversion 1"
     ),
...
...

As you can see I have changed the range to "0.0...200" because the slider shouldn't go beyond 200. I have entered the part of the calculation "/ 1000" into calc. This is the thing I'd like to add to the calculation in the DetailView (see below). I am not using the rest of the data right now, because I am still stuck with this calculation.

Code:

@State var sliderValue: Double = 0.0
var conversion: Converter

var body: some View {
    VStack(alignment: .leading) {
        VStack(alignment: .leading) {
            Text("value1".uppercased())
                .font(.largeTitle)
                .fontWeight(.bold)
            Text("\(sliderValue, specifier: "%.2f")")
                .font(.system(size: 60, weight: .bold))
            Text("to value2".uppercased())
                .font(.largeTitle)
                .fontWeight(.bold)
        
            self.calculatedView(for: sliderValue)
        }
    
        Slider(value: $sliderValue, in: conversion.range, step: 0.5)
            .padding(8)
            .overlay(
                Capsule()
                    .stroke(Color.purple, style: StrokeStyle(lineWidth: 5))
            )
    }
    .padding()
}

As you can see I use "conversion.range" to specify the exact range for the slider. Here is the calculation:

private func calculatedView(for value: Double) -> some View {
    Text("\(value / 1000, specifier: "%.2f")")
        .font(.system(size: 60, weight: .bold))
    }
}

Update:

So what I did was to try to use NSExpression in calculatedView:

private func calculatedView(for value: Double) -> some View {
    
    let calculation = String(value) + conversion.calc
    let expn = NSExpression(format:calculation)
    let result = expn.expressionValue(with: nil, context: nil)
    
    Text(result)
        .font(.system(size: 60, weight: .bold))
}

I am not even sure if this is the way to use NSExpression. I am trying to create a string from the value of the slider and add conversion.calc. It should look something like this: 125.5 / 1000 (just as an example). I am not sure if this is right in the first place. Now I try to use NSExpression and store the result in the variable result and this variable should be shown as a TextView. I get two error messages:

  1. Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type

And for Text(result): 2. No exact matches in call to initializer

Question

Right now I just add the value and hard-code the "/ 1000" in. What I'd like to change is instead use converter.calc (which is "/ 1000). But obviously when I just add this the whole calculation is broken, but I have no idea how the re-format the whole thing so that it works. Before I would have just created a switch statement (or something else) with a new calculation for each conversion, which would blow up the whole code.

I really hope someone can point me into the right direction, because I am so happy that I have finally understood how to use a simple DataModel.

Thanks for reading and for any tip. With best regards!

New Dev
  • 48,427
  • 12
  • 87
  • 129
rjbcg81
  • 61
  • 1
  • 4

1 Answers1

0

Rather have the calc property in Converter hold a closure. This way you could pass in any sort of conversion function when initializing a Converter (by passing in a conversion closure). Perform all calculations in Double (as numbers) and then convert to String for displaying.

Here is a link to Closures at Swift.org

It's going to be a huge mess trying to perform maths with Strings and Numbers.

EDIT Simple example of using Closure

All else being the same except for the calc variable

struct Converter {
    var calc: ((Double) -> Double)
}

Usage

let closure: ((Double) -> Double) = { num in
        return num / 1000
}

let converter = Converter(calc: closure)

print("Conversion result: \(converter.calc(500))")

You could pass any sort of calculation method into the converter, or you could hard the method in the struct itself and just call calc which will auto grab any variables within the struct.

But for your issue instead of the CalculatedView function:

VStack(alignment: .leading) {
        Text("value1".uppercased())
            .font(.largeTitle)
            .fontWeight(.bold)
        Text("\(sliderValue, specifier: "%.2f")")
            .font(.system(size: 60, weight: .bold))
        Text("to value2".uppercased())
            .font(.largeTitle)
            .fontWeight(.bold)
    
        Text("\(conversion.calc(sliderValue))")
            .font(.system(size: 60, weight: .bold))
    }
Alexander
  • 1,424
  • 18
  • 23
  • I tried this yesterday evening, but I cannot get this to work. Do you have any suggestion on how to accomplish this with a Closure? Thanks so much. Remind you I am still new to coding. – rjbcg81 Aug 05 '20 at 09:58