Text field

If you're getting input from a text field, you give a string as the input

but what if you want the input from the text field to be used later, in other lines in your program, as an Int, Double, CGfloat, etc?

how do I do that?

Hi @arduinolego611,

I've moved your question to "Using Swift" since it's not about developing the Swift compiler. Generally, questions about using Apple's UI frameworks are best asked over in Apple's Developer Forums.

However, your specific question is about converting strings to values of other types, which is very much a basic function of the programming language that we cover here on these forums. This is something that each destination type will decide for itself how to spell, but for standard library types like Int and Double, you can generally convert from a string value str by writing Int(str) or Double(str).

Pay attention to the preconditions for calling any such initializers. While some are failable initializers (i.e., they will return nil if you didn't pass a value that can be converted), others will expect that you have first validated certain properties of your input string and can crash your program if you don't.

You will also want to pay attention to other nuances in the documentation as sometimes you'll get a converted value that isn't what you'd expect because some character in the string is interpreted in some particular way.

3 Likes

Good answer, but I might suggest using NumberFormatter to convert user input text to a number (or vice versa). User input is generally localized (as is representing a number as a localized string). The Int(str), Double(str), etc., are not localized. Thus you might do things like:

let formatter = NumberFormatter()
formatter.numberStyle = .decimal

guard
    let input = self.textField.text,
    let value = formatter.number(from: input)?.doubleValue
else {
    return
}

// use `value` here

The key point is that different locales use differing “thousands” separators and “decimal point”. NumberFormatter is the safe and idiomatic way of converting the text from a text field to a numeric value, and back.

The above is the typical UIKit/AppKit way of dealing with this. In SwiftUI, you’d still use a number formatter, but the applicable syntax is slightly different. But the OP didn’t mention SwiftUI, so I’ll leave it at the above.

3 Likes

maybe someone could explain my errors

I have been looking at references for the syntax to unwrap this optional, can't really figure it out...I think also my conversion of string to double is wrong

or if there are more problems, I would appreciate the help

import SwiftUI

struct ContentView: View {
//    @State private var xRadius = 150   // default values for optional
//    @State private var yRadius = 75   // default values for optional
//    
//    @State private var: Double(xRadius2) = 0
//    @State private var: Double(yRadius2) = 0
    
    @State private var xRadiusInput: String? = " "
    @State private var yRadiusInput: String? = " "
    
        var xRadius: CGFloat
        {
            CGFloat(Double?(xRadiusInput) ?? 150)
        }
        
        var yRadius: CGFloat
        {
            CGFloat(Double?(yRadiusInput) ?? 75)
        }
   
    
    var speed: CGFloat = 0.5 
 
    var body: some View {
        TimelineView(.animation) { timeline in
            var speed = (xRadius + yRadius) / 100
            let time = timeline.date.timeIntervalSinceReferenceDate
            let angle = time * speed * 2 * .pi   
                    
            let x = cos(angle) * xRadius //angle is where you are on the circle, cos and sin are the x                           
            let y = sin(angle) * yRadius
                                            

            
            Text("X dimension: , \(xRadius)")
            TextField("Enter x dimension of elliptical orbit", text: $xRadiusInput)
                .textFieldStyle(RoundedBorderTextFieldStyle())
            Text("Y dimension:  \(yRadius)")
            TextField("Enter y dimension of elliptical orbit", text: $yRadiusInput)
                .textFieldStyle(RoundedBorderTextFieldStyle())
            
            
            ZStack {
                Ellipse()
                    .stroke(Color.gray.opacity(0.3), lineWidth: 2)
                    .frame(width: xRadius * 2, height: yRadius * 2)
                    // .offset(x: 50, y: 3)

                Circle()
                    .fill(Color.blue)
                    .frame(width: 30, height: 30)
                    .offset(x: x, y: y)  // x / y as in lines 22 and 23
                    
                
                Circle()
                    .fill(Color.red)
                    .frame(width: 75, height: 75)
            }
            .frame(width: xRadius * 2 + 60, height: yRadius * 2 + 60)
        }
    }
}

#Preview {
    ContentView()
}

I posted some code in my own post on this topic. Should I open it as a new topic???

I posted some code in my own post on this topic. Should I open it as a new topic?

If you are using SwiftUI, you would not use a String for your @State variable, but rather use a numeric type. And you might use TextField(_:value:format:prompt:) with a format of .number:

struct ContentView: View {
    @State private var xRadius: Double = 150
    @State private var yRadius: Double = 75

    …

    var body: some View {
        TimelineView(.animation) { timeline in
            …

            Text("X dimension: \(xRadius, format: .number)")
            TextField("Enter x dimension", value: $xRadius, format: .number)

            Text("Y dimension: \(yRadius, format: .number)")
            TextField("Enter y dimension", value: $yRadius, format: .number)

            …
        }
    }
}

The above was introduced in iOS 15, macOS 12, etc.. In earlier OS versions, one would use a NumberFormatter. In this case, one would use TextField(_:value:formatter:) to bind that text field to a numeric value, using the NumberFormatter to convert to and fro:

struct ContentView: View {
    @State private var xRadius: Double = 150
    @State private var yRadius: Double = 75

    private let formatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()

    …

    var body: some View {
        TimelineView(.animation) { timeline in
            …

            Text("X dimension: \(xRadius, format: .number)")
            TextField("Enter x dimension", value: $xRadius, formatter: formatter)

            …

            Text("Y dimension: \(yRadius, format: .number)")
            TextField("Enter y dimension", value: $yRadius, formatter: formatter)

            …
        }
    }
}

But either way, the underlying idea is the same, that you bind your TextField directly to the numeric state variable using the value renditions of the TextField initializers. In both cases, the formatter will convert back and forth between the user’s text entry and the numeric value.

3 Likes

at this part: TimelineView(.animation) { timeline in

im now getting this error message:

Generic parameter 'Content' could not be inferred

so sorry but I am confused again

When you have a syntax error inside TimelineView, the compiler can sometimes manifest that as you’ve outlined, rather than producing an error message at the actual offending line. We obviously cannot comment without seeing your implementation.

In case it helps you, here is an implementation that does not produce that error:

struct ContentView: View {
    @State private var xRadius: Double = 150
    @State private var yRadius: Double = 75

    var speed: CGFloat = 0.5

    var body: some View {
        TimelineView(.animation) { timeline in
            let speed = (xRadius + yRadius) / 100
            let time = timeline.date.timeIntervalSinceReferenceDate
            let angle = time * speed * 2 * .pi

            let x = cos(angle) * xRadius // `angle` is where you are on the circle, `cos` and `sin` are the `x` and `y`, respectively
            let y = sin(angle) * yRadius

            Text("X dimension: \(xRadius, format: .number)")
            TextField("Enter x dimension", value: $xRadius, format: .number)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            Text("Y dimension: \(yRadius, format: .number)")
            TextField("Enter y dimension", value: $yRadius, format: .number)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()

            ZStack {
                Ellipse()
                    .stroke(Color.gray.opacity(0.3), lineWidth: 2)
                    .frame(width: xRadius * 2, height: yRadius * 2)

                Circle()
                    .fill(Color.blue)
                    .frame(width: 30, height: 30)
                    .offset(x: x, y: y)

                Circle()
                    .fill(Color.red)
                    .frame(width: 75, height: 75)
            }
            .frame(width: xRadius * 2 + 60, height: yRadius * 2 + 60)
        }
    }
}

FWIW, I find that this “generic parameter … could not be inferred” problem is diminished if I separate out the random computational code from the Content of the TimelineView. E.g., perhaps:

struct ContentView: View {
    @State private var xRadius: Double = 150
    @State private var yRadius: Double = 75

    var speed: CGFloat = 0.5

    var body: some View {
        TimelineView(.animation) { timeline in
            Text("X dimension: \(xRadius, format: .number)")
            TextField("Enter x dimension", value: $xRadius, format: .number)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            Text("Y dimension: \(yRadius, format: .number)")
            TextField("Enter y dimension", value: $yRadius, format: .number)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()

            ZStack {
                Ellipse()
                    .stroke(Color.gray.opacity(0.3), lineWidth: 2)
                    .frame(width: xRadius * 2, height: yRadius * 2)

                Circle()
                    .fill(Color.blue)
                    .frame(width: 30, height: 30)
                    .offset(offset(at: timeline))

                Circle()
                    .fill(Color.red)
                    .frame(width: 75, height: 75)
            }
            .frame(width: xRadius * 2 + 60, height: yRadius * 2 + 60)
        }
    }

    private func offset(at timeline: TimelineViewDefaultContext) -> CGSize {
        let speed = (xRadius + yRadius) / 100
        let time = timeline.date.timeIntervalSinceReferenceDate
        let angle = time * speed * 2 * .pi

        let x = cos(angle) * xRadius //angle is where you are on the circle, cos and sin are the x
        let y = sin(angle) * yRadius

        return CGSize(width: x, height: y)
    }
}

If I now change something that introduces an error, it is much better at identifying the source of the problem. I also think this is cleaner, but perhaps that is subjective. Anyway, this confusing error message is a known problem when mixing non-view code inside a @ViewBuilder closure.

1 Like