Search code examples
iosswiftibaction

Are there any ways to access a variable modified in an IBAction?


Here is what I am trying to do:

var usernameCheckerResponse : String = ""

 //This IBAction is a UITextfield that sends post request when editing is finshed.
 @IBAction func usernameChecker(_ sender: Any) {

 // perform post request with URLSession
 // post request returns url response from URLSession 
 // the value of this response is either 'usernameExists' or 'usernameAvailable'
 // usernameCheckerResponse = String(describing : response) 

}
//use modified usernameCheckerResponse variable outside the IBAction function. 
//For example like this:

   func UsernameExists () -> Bool {
   if(usernameCheckerResponse == "usernameExists"){
   return true
  } else { return false }
}

I am aware that an IBAction will only return a void, so is there anyway around this problem? Any help and/or advice will be greatly appreciated.


Solution

  • In general, you should think of IBAction functions as connection points for controls like buttons etc. You would never call it yourself. If you need to do that, make another function and have the IBAction function call that.

    Since you are using URLSession to fetch the data from an external source, you will need to be aware that this does not happen synchronously. Send the call to your API and have the completion handler get called when data is returned.

    All of this code goes into your ViewController

    // Set up a reusable session with appropriate timeouts
    internal static var session: URLSession  {
        let sessionConfig = URLSessionConfiguration.default
        sessionConfig.timeoutIntervalForRequest = 6.0
        sessionConfig.timeoutIntervalForResource = 18.0
    
        return URLSession( configuration: sessionConfig )
    }
    
    
    // Create an httpPost function with a completion handler
    // Completion handler takes :
    //   success: Bool true/false if things worked or did not work
    //   value: String string value returned or "" for failures
    //   error: Error? the error object if there was one else nil
    func httpPost(_ apiPath: String, params: [String: String], completion:@escaping (Bool, String, Error?) -> Void) {
        // Create POST request
        if let requestURL = URL( string: apiPath ) {
            print("requestUrl \(apiPath)")
            // Create POST request
            var request = URLRequest( url: requestURL )
            request.httpMethod = "POST"
    
            var postVars : [String : String ] = params
            var postString = postVars.toHttpArgString()
    
            request.httpBody = postString.data( using: String.Encoding.utf8, allowLossyConversion: true )
    
            let sendTask = ViewController.session.dataTask( with: request) {
                (data, response, error) in
    
                if let nserror = error as NSError? {
                    // There was an error
                    // Log it or whatever
                    completion(false, "", error)
                    return
                }
    
                // Here you handle getting data into a suitable format
    
                let resultString = "whatever you got from api call"
                // Send it back to the completion block
                completion(true, resultString, nil)
            }
            sendTask.resume()
        }
    }
    // I assume you have a text field with the user name you want to try
    @IBOutlet weak var usernameToCheck : UITextField!
    @IBAction func usernameChecker(_ sender: Any) {
        guard let username = usernameToCheck.text else {
            // This is unlikely to happen but just in case.
            return
        }
        httpPost("https://someapicall", params: ["username" : username] ) {
            (success, value, error) in
            // This code gets called when the http request returns data.
            // This does not happen on the main thread.
            if success {
                if value == "usernameExists" {
                    // User name already exists. Choose a different one.
                    DispatchQueue.main.async {
                        // put code here if you need to do anything to the UI, like alerts, screen transitions etc.
                    }
                }
                else if value == "usernameAvailable" {
                    // You can use this user name
                    DispatchQueue.main.async {
                        // put code here if you need to do anything to the UI, like alerts, screen transitions etc.
                    }
                }
                else {
                    // Unexpected response from server
                }
    
            }
            else {
                // Something did not work
                // alert "Unable to connect to server"
            }
    
        }
    }
    

    To make this code work you will need this:

    // Syntatic sugar to convert [String:String] to http arg string
    protocol ArgType {}
    extension String: ArgType {}
    extension Dictionary where Key: ArgType,  Value: ArgType  {
        // Implement using a loop
        func toHttpArgString() -> String {
            var r = String()
            for (n, v) in self {
                if !r.isEmpty { r += "&" }
                r += "\(n)=\(v)"
            }
            return r
        }
    
    }