1

I am building a survey where when a user answers a question I append the answers to a url string with parameters and send a get request to my server. So for every answer there is a record made of the selected answer, timestamp and unique id of the survey.

I am not sure the best way to do this, but this is what I have so far.

I create the url and query items.

var urlComponents: URLComponents {

    let resultID = surveyQuestions.resultId
    print("\(String(describing: resultID))")

    let resultResponseID = surveyQuestions.questions[surveyResultResponseId]
    print("\(String(describing: resultResponseID))")

    let questionIndex = questionNumbers
    print("\(String(describing: questionIndex))")

    var urlComponents = URLComponents(string: "My String URL")

    urlComponents?.queryItems = [
        URLQueryItem(name: "surveyResultsId", value: "\(String(describing: resultID))"),
        URLQueryItem(name: "surveyResultsResponseId", value: "\(String(describing: resultResponseID))"),
        URLQueryItem(name: "questions", value: "\(questionIndex)"),
        URLQueryItem(name: "selectedAnswer", value: "\(storedAnswer)")

    ] 

    let url = urlComponents?.url

    print(url!.absoluteString as Any)

    return urlComponents!
}

Then I build the send request.

func sendRequest(_ url: String, parameters: [String: String], completion: @escaping ([String: Any]?, Error?) -> Void) {

    var components = URLComponents(string: url)!
    components.queryItems = parameters.map { (key, value) in
        URLQueryItem(name: key, value: value)
    }

    components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
    let request = URLRequest(url: components.url!)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, // is there data
            let response = response as? HTTPURLResponse, // is there HTTP response
            (200 ..< 300) ~= response.statusCode, // is statusCode 2XX
            error == nil else { // was there no error, otherwise ...
                completion(nil, error)
                return
        }

        let responseObject = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
        completion(responseObject, nil)
        print("This is the \(responseObject!)")
    }
    task.resume()
}

And finally I call the send request when an answer is pressed.

@IBAction func answerPressed(_ sender: UIButton) {

    if sender.tag == selectedAnswer {
        questionNumbers += 1
    }
    storedAnswer = [sender.tag]
//        storedAnswer.append(sender.tag)
    print(storedAnswer)

    sendRequest("\(urlComponents)", parameters: ["": ""]) { responseObject, error in
        guard let responseObject = responseObject, error == nil else {
            print(error ?? "Unknown error")
            return
        }


        // use `responseObject` here
    }

    questionNumbers += 1
    updateQuestion()
}

Now when I run this I get back the string with the query items, but when I run the send request I get unknown error. I feel as if I am doing something wrong. For the area "use responseObject here" what do I put in there. Im a little confused. Also when I call the send request what should I put in the parameter values. Right now they are just parameters: ["": ""]. I feel as if I am close. Any help is much appreciated.

jfulton
  • 69
  • 10

1 Answers1

2

First off, you're doing a lot more work than is probably necessary. You're encoding your query string parameters into URLComponents which is correct. Then, in your send you are decomposing your URL and parsing out the components then re-encoding them. You're also doing a lot of force-unwrapping, which is fragile and hides problems.

Here's your code simplified in a playground that works for me:

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let sampleURL = "https://someserver.com/somepath"

func sendRequest(_ url: URL, completion: @escaping ([String: Any]?, Error?) -> Void) {

    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data, // is there data
            let response = response as? HTTPURLResponse, // is there HTTP response
            (200 ..< 300) ~= response.statusCode, // is statusCode 2XX
            error == nil else { // was there no error, otherwise ...
                completion(nil, error)
                return
        }
        let responseObject = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
        completion(responseObject, nil)
    }
    task.resume()
}

var urlComponents: URLComponents? {

    let resultID = "resultID123"
    let resultResponseID = "responseID456"
    let questionIndex = "questionNumbers1"

    var urlComponents = URLComponents(string: sampleURL)
    urlComponents?.queryItems = [
        URLQueryItem(name: "surveyResultsId", value: "\(String(describing: resultID))"),
        URLQueryItem(name: "surveyResultsResponseId", value: "\(String(describing: resultResponseID))"),
        URLQueryItem(name: "questions", value: "\(questionIndex)"),
        URLQueryItem(name: "selectedAnswer", value: "\("storedAnswer1")")

    ]
    return urlComponents
}

if let urlComponents = urlComponents, let url = urlComponents.url?.absoluteURL {
    sendRequest(url) { (result, error) in
        print("Got an answer: \(String(describing: result))")
    }
}

When I run this against a server URL that returns valid JSON, I get:

Got an answer: Optional(["image": {
    href = "https://example.com";
}, "object_types": {
    card =     {
        fields =         {
        };
        pollable = 1;
    };
}])
David S.
  • 6,043
  • 1
  • 23
  • 44
  • Thanks for the quick response. For the if let urlcomponents = urlcomponents, I get this error 'let' cannot appear nested inside another 'var' or 'let' pattern. Not sure why. – jfulton Sep 16 '19 at 20:38
  • When I put the if let urlComponents = urlComponents, let url = urlComponents.url?.absoluteURL { sendRequest(url) { (result, error) in print("Got an answer: \(String(describing: result))") } } inside my answerPressed button it works fine. is that how I should use it? – jfulton Sep 16 '19 at 20:46
  • I also noticed that the response I get when sending the request doesn't include the url and urlqueryitems. It just returns an optional response with no urlcomponents. – jfulton Sep 16 '19 at 21:26
  • URLQueryItems is just a construct for encoding parameters into the Query String. When you call "absoluteURL" on the urlComponents, it encodes all the components into the URL itself (e.g https://example.com/?surveyResultsId=resultID123&surveyResultsResponseId=responseID456&questions=questionNumbers1&selectedAnswer=storedAnswer1). You can always recover them if need be, but they are in there. – David S. Sep 17 '19 at 14:22
  • Is there a way to remove the ? before the queryitems? I tried using replacingOccurrences(of: "?", with: "") on sampleUrl, but that didn't work. – jfulton Sep 25 '19 at 21:52
  • @jfulton certainly there are, but that will change the nature of the URL. It will change the query string parameters into path parameters. More than likely the result won't be something your server can deal with. – David S. Sep 25 '19 at 22:42
  • Actually it will. The server doesn't accept the query String with the ?. Only if i remove it. – jfulton Sep 25 '19 at 23:30
  • It seems like a different question. Maybe ask a new one with the desired URL and we can help you construct one. – David S. Sep 26 '19 at 02:05