How can I format a space that is entered into textfield and code the space into a URL?

I'm working on a weather app where I type in the name of the city in the text field, the app then hits the Open Weather Map API, and returns the weather for that location. It works if the city name is only one word, like Chicago, but a city with two words in it, such as Los Angeles, doesn't work.

I'm new to programming and my understanding is that to indicate a space in a URL, the space needs to be represented as "%20".

Checking it in my browser, adding "&q=Los%20Angeles" to the end of the URL works.

What's the best way to incorporate the possibility of a space being entered in a name in the text field, and then formatting that string w/a space into a URL?





Code Block
import UIKit
import CoreLocation
protocol APIManagerDelegate {
  func didUpdateData(_ APIManager: APIManager, data: APIManager)
  func didFailWithError(error: Error)
}
struct APIManager {
   
  let dataURL = "https://api.openweathermap.org/data/2.5/weather?appid=MyAPIID&units=imperial"
   
  var delegate: APIManagerDelegate?
   
  func fetchData(location: String) {
    let urlString = "\(dataURL)&q=\(location)"
    performRequest(urlString: urlString)
    /*print(urlString)*/
  } 
 
   
   func performRequest(urlString: String) {
    if let url = URL(string: urlString) {
      let session = URLSession(configuration: .default)
      let task = session.dataTask(with: url) { (data, response, error) in
        if error != nil {
          print(error!)
          return
        }
        if let safeData = data {
          self.parseJSON(cityData: safeData)  
        }
      }
      task.resume()
    }
  }
   
  func parseJSON(cityData: Data) {
    let decoder = JSONDecoder()
    do {
      let decodedData = try decoder.decode(APIResults.self, from: cityData)
      /*print(decodedData.name.name)*/
      print(decodedData.main.temp)
      print(decodedData.coord.lat)
      print(decodedData.coord.lon)
    } catch {
      print(error)
    }
  }
}

Answered by GettinSwifty in 673007022
Thanks for the replies, I had to no idea URLComponents was a thing.

I've reformatted the code to look like this:

Code Block import UIKit
import CoreLocation
protocol APIManagerDelegate {
  func didUpdateData(_ APIManager: APIManager, data: APIManager)
  func didFailWithError(error: Error)
}
struct APIManager {
  
  let dataURL = "https://api.openweathermap.org/data/2.5/weather?"
  let appId = "appid="
  let myApiKey = "..."
  var delegate = APIManagerDelegate?.self
  func fetchData(location: String) {
    var urlComponents = URLComponents(string: dataURL)
    urlComponents?.queryItems = [
      URLQueryItem(name: "appId", value: appId),
      URLQueryItem(name: "units", value: "imperial"),
      URLQueryItem(name: "q", value: location),
    ]
    performRequest(url: urlComponents?.url)
  }
   
  func performRequest(url: URL?) {
    if let url = url {
      let session = URLSession(configuration: .default)
      let task = session.dataTask(with: url) { (data, response, error) in
        if error != nil {
          print(error!)
          return
        }
        if let safeData = data {
          self.parseJSON(cityData: safeData)
        }
      }
      task.resume()
    }
  }
   
   
  func parseJSON(cityData: Data) {
    let decoder = JSONDecoder()
    do {
      let decodedData = try decoder.decode(APIResults.self, from: cityData)
      print(decodedData.name)
      print(decodedData.main.temp)
      print(decodedData.coord.lat)
      print(decodedData.coord.lon)
    } catch {
      print(error)
    }
  }
}


however nothing is printing in the debug console, so I'm thinking there may still be a problem w/how the URL is being formatted. Is there something I'm missing/ not understanding?

What's the best way to incorporate the possibility of a space being entered in a name in the text field, and then formatting that string w/a space into a URL?

I cannot say what would be the best, but can say using URLComponents would be a very preferable way:
Code Block
struct APIManager {
//let dataURL = "https://api.openweathermap.org/data/2.5/weather?appid=MyAPIID&units=imperial"
let weatherURL = "https://api.openweathermap.org/data/2.5/weather"
let MyAPIID = "MyAPIID"
var delegate: APIManagerDelegate?
func fetchData(location: String) {
var urlComponents = URLComponents(string: weatherURL)!
urlComponents.queryItems = [
URLQueryItem(name: "appid", value: MyAPIID),
URLQueryItem(name: "units", value: "imperial"),
URLQueryItem(name: "q", value: location),
]
performRequest(url: urlComponents.url)
/*print(urlComponents.url)*/
}
func performRequest(url: URL?) {
if let url = url {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(cityData: safeData)
}
}
task.resume()
}
}
//...
}


As OOPer stated, URL components is the safest.

Otherwise, you could add manually:
declare dataUrl as var
and
dataUrl = dataUrl.replacingOccurrences(of: " ", with: "%20")

But components is safer way to go.
Accepted Answer
Thanks for the replies, I had to no idea URLComponents was a thing.

I've reformatted the code to look like this:

Code Block import UIKit
import CoreLocation
protocol APIManagerDelegate {
  func didUpdateData(_ APIManager: APIManager, data: APIManager)
  func didFailWithError(error: Error)
}
struct APIManager {
  
  let dataURL = "https://api.openweathermap.org/data/2.5/weather?"
  let appId = "appid="
  let myApiKey = "..."
  var delegate = APIManagerDelegate?.self
  func fetchData(location: String) {
    var urlComponents = URLComponents(string: dataURL)
    urlComponents?.queryItems = [
      URLQueryItem(name: "appId", value: appId),
      URLQueryItem(name: "units", value: "imperial"),
      URLQueryItem(name: "q", value: location),
    ]
    performRequest(url: urlComponents?.url)
  }
   
  func performRequest(url: URL?) {
    if let url = url {
      let session = URLSession(configuration: .default)
      let task = session.dataTask(with: url) { (data, response, error) in
        if error != nil {
          print(error!)
          return
        }
        if let safeData = data {
          self.parseJSON(cityData: safeData)
        }
      }
      task.resume()
    }
  }
   
   
  func parseJSON(cityData: Data) {
    let decoder = JSONDecoder()
    do {
      let decodedData = try decoder.decode(APIResults.self, from: cityData)
      print(decodedData.name)
      print(decodedData.main.temp)
      print(decodedData.coord.lat)
      print(decodedData.coord.lon)
    } catch {
      print(error)
    }
  }
}


however nothing is printing in the debug console, so I'm thinking there may still be a problem w/how the URL is being formatted. Is there something I'm missing/ not understanding?
I didn't have my API key in the proper place, it works now.
Just a comment on the use of the forum.

OOPer provided you with the right answer, that you put into your code. That's great.

But the answer to mark as correct was OOPer's, not yours (that was not even correct in fact).

Do it properly next time.

Have a good day.
Yeah that was an accident, I tried to fix it but I guess I can't.
How can I format a space that is entered into textfield and code the space into a URL?
 
 
Q