Save IP Adr as "string" from user input and save as variable

Hi Dev Forum

I have this piece of SwiftUI code that present an Input field for the user on iOS, Mac to enter an IP address, the entered IP I want to save in a variable so it can be inserted in my further http requests to a server API but it seems like I'm not doing it right.

Code Block
import SwiftUI
// Input of Controller IP
struct ServerIPInput: View {
    @State var CTRLIP: String = ""
    var body: some View {
        VStack(alignment: .leading) {
            Text("Enter Control IP")
                .font(.callout)
                .bold()
            TextField("Enter Control IP...", text: $CTRLIP)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }.padding()
    }
  }

I then have this code somewhere else in same file, but this is in a new "struct"

Code Block
let servIp = "$CTRLIP"     // here I would need this entered IP  

How can I make the user entered "CTRLIP" equal to my let statement for servIp?

Any help with this would be great.

Thanks
Me
Answered by Tylor in 648430022
@Enevold,

I looked at what you have, and it did confirm what I was thinking that you are trying to use global variables. I would suggest refactoring your code and look into some tutorials on how SwiftUI handles the movement of data. Below I have modified your program some:

Code Block Swift
struct ContentView: View {
    @State private var servIp: String = "192.168.0.196"
    let port: Int = 8000
    let cmd: String = "xmlcommand"
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text("Enter Control IP")
                TextField("Enter Control IP...", text: $servIp)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
            }
            Button(action: {
                sendHttpPostDesktops()
            }, label: {
               Text("Desktops")
            }).buttonStyle(PlainButtonStyle())
        }
    }
    func sendHttpPostDesktops() {
        print("servIP = \(servIp)")
    }
}

I removed your ServerIPInput struct because I didn't see where you called this view ContentView. In the example above, servIp will be whatever the user enters into the textfield; otherwise, it will be keep the initial value of 192.168.0.196. If you want another struct view, you should look into how to bind to a state variable to create a connection for passing data between views. My final suggestion would be looking into making an object that contains all the server logic. For example, you can make an observable object that contains the sendHttpPostDesktops function. This way, it separates your view and data logic.
Can you show the full code which uses ServerIPInput and CTRLIP.

(This is not directly related to your issue, but using an identifier of Capital letters looks very odd in Swift.)

You may need to make your @State to @Binding, to show some example code, I need to see how ServerIPInput is used.
hi OOper

i just made it capital letters in the post to malke it more visible it could just be ctrlip
i had the let servIp = “192.168.1.100” which works when it is static ip.
so in general i only want the user typed ip to be that variable.

/me


Hi OOper

I'm not sure I understand your question, actually I only have this testing code, as well as a "func" that does the http post commands against the IP I want to enter on the input.

/Me
@Enevold,

Your code does work and CTRLIP would contain the text of whatever the user entered in the textfield. Based on what information you have given, you seem to be treating CTRLIP as a global variable. You mention that you have this line in another struct:
Code Block Swift
let servIp = "$CTRLIP" 

If this line is in another struct, "$CTRLIP" is just a string nothing to do with the State variable in the ServerIPInput view. What OOPer was referring to was posting that struct that contains the servIp variable to see why it isn't connected.

It is unclear to see how your struct that contains servIp is getting the value from the ServerIPInput view. If ServerIPInput is a child view of this struct that contains servIp, you can turn CTRLIP into a Binding variable which will create the connection for data to transfer back to the parent view, but the parent view would need the State.
Sorry, I think I do not understand what you are saying.

Anyway, there's nothing left that I can do under the currently shown info.
deleted post
Hi OOPer

Sorry for my delay, here is how it looks like for SwiftUI code for testing the User Input.
and I think it is me that that does not understand SwiftUI yet enough to express it correct, more likely ;-))


Code Block
import SwiftUI
// Control API Variables
        let servIp = "192.168.0.196"        // Comment: This IP string should be taken from the user input
// or this way
// let servIp = "$CTRLIP"
        let port = 8000                     // Int
        let cmd = "xmlcommand"           // String
// Input of Controller IP
struct ServerIPInput: View {
    @State var CTRLIP: String = ""
    var body: some View {
        VStack(alignment: .leading) {
            Text("Enter Control IP")
                .font(.callout)
                .bold()
            TextField("Enter Control IP...", text: $CTRLIP)
                .textFieldStyle(RoundedBorderTextFieldStyle())
              }.padding()
        }
    }
    struct ContentView: View {
       var body: some View {
            // Button
                Button(action: {sendHttpPostDesktops()}) {
                       Text("Desktops")
                               .padding(20)
                               .background(/*@START_MENU_TOKEN@*//*@PLACEHOLDER=View@*/Color.blue/*@END_MENU_TOKEN@*/)
                               .foregroundColor(.white)
                               .border(Color.white, width: 4)
                               .cornerRadius(8)
                }.buttonStyle(PlainButtonStyle())
            }
            // more buttons.
            // more buttons.
            // more buttons.
        }
// Functions
     // Open Environment called Desktops
func sendHttpPostDesktops() {
       let session = URLSession(configuration: .default)
       let url = URL(string: "http://\(servIp):\(String(port))/\(cmd)")!
       var request = URLRequest(url: url)
       request.httpMethod = "POST"
       request.setValue("text/plain", forHTTPHeaderField: "Content-Type") // I guess this can be "text/xml"
       // Data line
       request.httpBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Commands><command type=\"open\"><name>environments/Desktops.hwe</name><id>a1</id></command></Commands>".data(using: .utf8)
       let task = session.dataTask(with: request) { data, response, error in
               print(data as Any? as Any)
               if let data = data {
                       print(String(data: data, encoding: .utf8)!)
               } else {
                       print("no data")
               }
       }
       task.resume() // <- otherwise your network request won't be started
}
}
struct ContentView_Previews: PreviewProvider {
       static var previews: some View {
       // var previews: some View {
       ContentView()
    }
}


Br
Michael

Accepted Answer
@Enevold,

I looked at what you have, and it did confirm what I was thinking that you are trying to use global variables. I would suggest refactoring your code and look into some tutorials on how SwiftUI handles the movement of data. Below I have modified your program some:

Code Block Swift
struct ContentView: View {
    @State private var servIp: String = "192.168.0.196"
    let port: Int = 8000
    let cmd: String = "xmlcommand"
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text("Enter Control IP")
                TextField("Enter Control IP...", text: $servIp)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
            }
            Button(action: {
                sendHttpPostDesktops()
            }, label: {
               Text("Desktops")
            }).buttonStyle(PlainButtonStyle())
        }
    }
    func sendHttpPostDesktops() {
        print("servIP = \(servIp)")
    }
}

I removed your ServerIPInput struct because I didn't see where you called this view ContentView. In the example above, servIp will be whatever the user enters into the textfield; otherwise, it will be keep the initial value of 192.168.0.196. If you want another struct view, you should look into how to bind to a state variable to create a connection for passing data between views. My final suggestion would be looking into making an object that contains all the server logic. For example, you can make an observable object that contains the sendHttpPostDesktops function. This way, it separates your view and data logic.
To go one step further (from @Tylor’s) in refactoring your code, pull the
Code Block
func sendHttpPostDesktops()
out of the ContentView struct, since the function’s concerns are not those of the view. Such a refactoring makes the function testable without firing up a view. Also, add a parameter to the function, like this:

Code Block
func sendHttpPostDesktops(ip: String) {
let session = ... // same code as before
let url = URL(string: "http://\(ip):\(String(port))/\(cmd)")!
... // the rest of your existing code
}


and invoke this method in your button’s action closure:

Code Block
Button(action: { sendHttpPostDesktops(ip: serverIp) }, label: {...}). ....


(Note that I have elided pieces of code from @Tylor’s example for brevity.)


You do want to write unit tests, especially for functions like
Code Block
sendHttpPostDesktops
to assure yourself that they work properly with bad and good inputs.


Thank you for all the suggestions, I will have a look into these examples.

/Me
Save IP Adr as "string" from user input and save as variable
 
 
Q