HTTP post request and save response in app - swift

I'm totally new to swift and iOS programming so I'm a little lost on how to do this and even in what files I should be doing this too.
I'm trying to do a http post request to get calendar events and save them in the app to later use and display.
I made a model class with this code.
import UIKit
class Event {
var id: Int
var init_date: String
var end_date: String
var title: String
var description: String
var color_code: String
var all_day: Int
init?(id: Int, init_date: String, end_date: String, title: String, description: String, color_code: String, all_day: Int) {
//Initialization should fail if these are false
if id < 0 || init_date.isEmpty || end_date.isEmpty || title.isEmpty {
return nil
}
//Initialize stored properties
self.id = id
self.init_date = init_date
self.end_date = end_date
self.title = title
self.description = description
self.color_code = color_code
self.all_day = all_day
}
}
But now I don't know what the next step would be. I need this to be downloaded immediately once the app is opened for the first time and not when it's not being opened for the first time. Do I create a new method in the ViewController.swift for the download?
Right now I haven't added anything to the ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
What should I do next?

At this point you need to create a function that handles the POST request you are making.
Once completed, place this function inside your appDelegate main function didFinishLaunchingWithOptions. This is the function that executes on appStart
On a successful function call save the data (presumably json) into a Global Variable or whatever you need for you app.
TIP:
On you class
class Event: Codable {
}
make sure to add Codable like above
Below is an example of what your post request will look like
func myPostRequest(completionHandler: #escaping (Bool?, String?) -> Void){
guard let url = URL(string:"") else { return }
let parameters = ["": ""]
var request: URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("", forHTTPHeaderField: "Authorization")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
error == nil
else {
print(error as Any)
return
}
if let httpResponse = response as? HTTPURLResponse {
if (httpResponse.statusCode == 200) {
if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
//print("^^^^^^^^^^^^^^",json)
for x in json ?? [] {
//here is where you will parse your data from the post request
}
completionHandler(true, nil)
return
}
} else {
completionHandler(false, "No Response From Server")
print("Failure response: STATUS CODE != 200")
}
} else {
completionHandler(false, "Database Connection Error")
print("Error \(error!)")
}
}
task.resume()
} catch let error {
completionHandler(false, "failure")
print("POSTERROR: \(error.localizedDescription)")
}
}

I use Alamofire, you can add it to your project via:
Pods
Swift Package Manager
When you add the framework you can use it:
import Alamofire
Then you need to make your class with the protocol Codable to pass the data to your class.
class Event: Codable { }
Then you need to call the url and store the response in a variable:
override func viewDidAppear(_ animated: Bool) {
AF.request("your API rest url").responseData { (resData) in
guard let data = resData.data else { return }//Check if the data is valid
do {
let decoder = JSONDecoder()//Initialize a Json decoder variable
let decodedData = try decoder.decode(Event.self, from: data)//Decode the response data to your decodable class
//Print the values
print(decodedData.headers)
print(decodedData.id)
print(decodedData.init_date)
print(decodedData.end_date)
} catch {
print(error.localizedDescription)
}
}
}

Related

Alamofire and TableView/CollectionView

I created a reusable Alamofire request which works smoothly. I am trying to get the data from the request Decode it(works fine too) and append it to one of my arrays to display in tableView/collectionView.
I am using MVVM and I append my data in viewModel(you can see below). The thing is I have tableView in my viewController and inside my tableView methods( viewForSupplementaryElementOfKind for instance) the 'stories'(from viewModel) are always empty.
In my viewDidLoad method I call getMainPageData(from viewModel) first and then create my tableView. I assure you the request itself is a success, the only problem is displaying the data.
Please keep in mind that the project has many API calls so I need a solution which will work in all cases when I have to deal with "lists". Thank you in advance
class NetworkManager {
let keychain = KeychainManager()
let base = "SomeBase"
let storageManager = StorageManager()
func setupRequest(path: Paths, method: RequestMethod, body: Encodable? = nil, params: [String: Any]? = nil, header: HeaderType, completion: #escaping((Result<Data,NetworkError>) -> Void)) {
var queries = ""
if let params = params {
queries = params.passingQuery()
}
let url = URL(string: base + path.rawValue + queries)
var request = URLRequest(url: url!)
request.httpMethod = method.rawValue
request.setValue(header.value[0].value, forHTTPHeaderField: "Content-Type")
if let userToken = keychain.getAccessToken(), userToken.count > 0 {
request.setValue("Bearer " + userToken, forHTTPHeaderField: "Authorization")
}
if let body = body {
if let jsonData = body.toJSONData() {
request.httpBody = jsonData
}
}
AF.request(request).validate().responseJSON { response in
if (200...299) ~= response.response?.statusCode ?? -1 {
self.handlingHeaders(response: response)
completion(.success(response.data!))
} else {
do {
if let data = response.data {
let json = try JSONDecoder().decode(ErrorResponse.self, from: data)
completion(.failure(.responseError(json.message)))
}
} catch {
completion(.failure(.serverError))
}
}
}
}
private func handlingHeaders(response: AFDataResponse<Any>) {
let headers = response.response?.headers
if let accessToken = headers?.dictionary["Authorization"] {
keychain.saveToken(token: accessToken)
}
}
}
extension Encodable {
func toJSONData() -> Data? { try? JSONEncoder().encode(self) }
}
var stories = [Story]()
func getMainPageData(completion: #escaping(Result<Void, NetworkError>) -> ()) {
networkManager.setupRequest(path: .mainPageData, method: .get, body: nil, params: nil, header: .application_json) { [self] result in
switch result {
case .success(let data):
do {
let homePageData = try JSONDecoder().decode(MainPageResponse.self, from: data)
stories.append(contentsOf: homePageData.model.stories)

Google Text-To-Speech in swift

I am creating an app that has the text as input and must give out the speech as the output. I would like to send a function that I have created for fetching the text which will be the input and have the the text-to-speech return as the output. I have created a model for decoding the data generated from the Json data below for both input (JokesAPI), and output(ChatBotAPI). I have created a view controller that will return a speech for when the user taps a button. I am wondering, how can I implement this button for returning two separate functions when the button is tapped, both the text that is generated from the first API, and the text-to-speech that is generated from the google api ? Below is the functions that I have created for both getting data back from the APIs, and the button that I would like use for both functions
// the function for the Text that the google text-speech will use as an input
func fetchJokes()
// The function that grabs the data from google
import Foundation
class ChatBotAPI {
var session: URLSession?
var API_KEY = doSomethingDope
func fetchTextToSpeech(key: String, completion: #escaping ([Voice]) -> Void, error errorHandler: #escaping (String?) -> Void){
guard let url = URL(string: "https://cloud.google.com/text-to-speech/docs/reference/rest/?apix=true#service:-texttospeech.googleapis.com")
else {
errorHandler("Invalid URL endpoint")
return
}
guard let key = API_KEY else {
print("Can not generate these parameters for the API Key")
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
errorHandler(error.localizedDescription)
return
}
guard let response = response
else {
errorHandler("Could not generate a response")
return
}
struct ChatBotResponse: Decodable {
let data: [Voice]
}
guard let data = data else {
errorHandler("No data")
return
}
do {
let chatBotResponse = try JSONDecoder().decode(ChatBotResponse.self, from: data)
completion(chatBotResponse.data)
} catch {
print("Error decoding chat bot response:", error)
errorHandler("Something went wrong")
}
}
task.resume()
}
}
//Model
// MARK: - Voice
struct Voice: Decodable {
let audioConfig: AudioConfig
let input: Input
let voice: VoiceClass
}
// MARK: - AudioConfig
struct AudioConfig: Decodable {
let audioEncoding: String
let effectsProfileID: [String]
let pitch, speakingRate: Double
enum CodingKeys: String, CodingKey {
case audioEncoding
case effectsProfileID = "effectsProfileId"
case pitch, speakingRate
}
}
// MARK: - Input
struct Input: Decodable {
let text: String
}
// MARK: - VoiceClass
struct VoiceClass: Decodable {
let languageCode, name: String
}
// ViewController with button
#IBAction func didPressBtn() {
// MARK: Actions for both fetching a joke from the API, & Text-Speech using AI Voice
}
func fetchJokes()
{
//whenever you get the response of jokes, call the function fetchTextToSpeech by passing jokes as string
let joke = "This is a joke. This is only a joke."
fetchTextToSpeech(strJokes: joke)
}
func fetchTextToSpeech(strJokes : String)
{
var speechSynthesizer = AVSpeechSynthesizer()
var speechUtterance: AVSpeechUtterance = AVSpeechUtterance(string: strJokes)
speechUtterance.rate = AVSpeechUtteranceMaximumSpeechRate / 3.0
speechUtterance.voice = AVSpeechSynthesisVoice(language: "en-US")
speechSynthesizer.speak(speechUtterance)
}
#IBAction func didPressBtn() {
fetchJokes()
}

URLSession cannot find 'self' in scope for errors and statusCode

My question is somewhat similar to 69959018, so I have made sure to clarify as much as I can
I'm trying to use the Steam Web API to create an app that grabs everyone on my friend list in the form of a JSON dictionary. I'm trying to use foundation instead of Alamofire in order to learn Foundation better.
So far, what I've done is the following in AppDelegate.swift:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
var apiKey: String = "[REDACTED]"
var steamID: String = "[REDACTED]"
let getPlayerSummaries = URL(string: "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=\(apiKey)&steamids=\(steamID)")
let friendList = downloadPlayerSummaries(with: getPlayerSummaries)
print(friendList)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
In another file I made called networkManager.swift, I have wrote this based on what I have found in the apple documentation for "Fetching Website Data into Memory" :
//
// networkManager.swift
// Who is online?
//
// Created by Dash Interwebs on 11/21/21.
//
import Foundation
func downloadPlayerSummaries(with: URL!) {
let url = with
if url == nil {
print("url is nil")
return
}
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
}
}
task.resume()
}
After this however, self.handleClientError(error), and self.handleServerError(response) complain about being unable to find "self". I can't find anything about handleServerError or handleClientError. So where exactly is "self" in this context? I think that it might be URLSession but I'm not too sure.
You can refactor your code using a completion handler and using an enum that conforms to the Error protocol:
enum ApiError: Error {
case network(Error)
case genericError
case httpResponseError
}
func downloadPlayerSummaries(with url: URL?, completion: #escaping (_ success: Bool, _ error: ApiError?) -> Void) {
guard let url = url else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(false, .network(error))
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
completion(false, .httpResponseError)
return
}
// then handle your data. The completion should also include the kind of data your want to return
}
task.resume()
}
I haven't tested. Let me know if it works.

Generic parameter 'T' could not be inferred: in Common Webservice method

I am trying to make generic post method for API call.In my loadNew method I want to add normal dictionary inside resource object.Resource contains normal data which will pass from controller class.And dictionary is passed as body of request. but while encoding "Generic parameter 'T' could not be inferred" showing. How do I use dictionary in it?
struct Resource<T> {
let url: URL
let request: URLRequest
let dictionary : [String:Any]
let parse: (Data) -> T?
}
final class Webservice {
// MARK:- Generic
func load<T>(resource: Resource<T>, completion: #escaping (T?) -> ()) {
URLSession.shared.dataTask(with: resource.url) { data, response, error in
if let data = data {
//completion call should happen in main thread
DispatchQueue.main.async {
completion(resource.parse(data))
}
} else {
completion(nil)
}
}.resume()
}
func loadNew<T>(resource: Resource<T>, completion: #escaping (T?) -> ()) {
var request = resource.request
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
//FIXIT: error is getting here
let jsonBody = try JSONEncoder().encode(resource.dictionary)
request.httpBody = jsonBody
}catch{}
let session = URLSession.shared
session.dataTask(with: request) { data, response, error in
if let data = data {
//completion call should happen in main thread
DispatchQueue.main.async {
completion(resource.parse(data))
}
} else {
completion(nil)
}
}.resume()
}
}
This method is called inside my Login controller.I have also tried assign it directly to request object but same error is showing
func APICall(){
guard let url = URL(string: Constants.HostName.local + Constants.API.User_Login) else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let resources = Resource<LoginReponse>(url: url, request: request, dictionary: dict){
data in
let loginModel = try? JSONDecoder().decode(LoginReponse.self, from: data)
return loginModel
}
// var response = LoginReponse()
Webservice().loadNew(resource: resources) {
result in
if let model = result {
print(model)
}
}
}
The error is a bit misleading, and may indicate you're using an older version of Xcode. In 11.4.1, the error is much more explicit:
error: value of protocol type 'Any' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
The problem is that [String: Any] is not Encodable, because there's no way to encode "Any" (what should happen if you passed a UIViewController here? Or a CBPeripheral?)
Instead of a dictionary here, looking at your code I would expect you to pass an encodable object here. For example:
struct Resource<Value: Decodable, Parameters: Encodable> {
let url: URL
let request: URLRequest
let parameters : Parameters?
let parse: (Data) -> Value?
}
final class Webservice {
func loadNew<Value, Parameters>(resource: Resource<Value, Parameters>, completion: #escaping (Value?) -> ()) {
var request = resource.request
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
if let parameters = resource.parameters {
request.httpBody = try? JSONEncoder().encode(parameters)
}
// ...
}
That said, I'd probably turn this system around a bit. If you want to have a Request<T> (parameterized on the thing it returns, and not on the parameters it takes to generate it), that's fine. You can pack a bit more into the struct. For example:
let baseURL = URL(string: "https://example.com/api/")!
struct Resource<Value> {
let urlRequest: URLRequest
let parse: (Data) -> Result<Value, Error>
// Things you want as default for every request
static func makeStandardURLRequest(url: URL) -> URLRequest {
var request = URLRequest(url: url)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
return request
}
}
// It would be nice to have a default parser when you can, but you don't have to put that
// into Webservice. The Resource can handle it.
extension Resource where Value: Decodable {
init(urlRequest: URLRequest) {
self.init(urlRequest: urlRequest, parse: { data in
Result { try JSONDecoder().decode(Value.self, from: data) }
})
}
}
And then Resources are smart about themselves:
struct LoginParameters: Encodable {
let username: String
let password: String
}
struct LoginResult: Decodable {
let authToken: String
}
extension Resource where Value == LoginResult {
static func login(parameters: LoginParameters) -> Resource {
var urlRequest = makeStandardURLRequest(url: baseURL.appendingPathComponent("login"))
urlRequest.httpBody = try? JSONEncoder().encode(parameters)
return Resource(urlRequest: urlRequest)
}
}
Of course that may get repeated a lot, so you can hoist it out:
extension Resource where Value: Decodable {
static func makeStandardURLRequest<Parameters>(endpoint: String, parameters: Parameters) -> URLRequest
where Parameters: Encodable {
var urlRequest = makeStandardURLRequest(url: baseURL.appendingPathComponent(endpoint))
urlRequest.httpBody = try? JSONEncoder().encode(parameters)
return Resource(urlRequest: urlRequest)
}
}
And then Login looks like:
extension Resource where Value == LoginResult {
static func login(parameters: LoginParameters) -> Resource {
return makeStandardURLRequest(endpoint: "login", parameters: parameters)
}
}
The point is that you can pull duplicated code into extensions; you don't need to stick it in the Webservice, or add more generic.
With that, your load gets a bit simpler and much more flexible. It focuses just on the networking part. That means that it's easier to swap out with something else (like something for unit tests) without having to mock out a bunch of functionality.
func load<Value>(request: Resource<Value>, completion: #escaping (Result<Value, Error>) -> ()) {
let session = URLSession.shared
session.dataTask(with: request.urlRequest) { data, response, error in
DispatchQueue.main.async {
if let data = data {
//completion call should happen in main thread
completion(request.parse(data))
} else if let error = error {
completion(.failure(error))
} else {
fatalError("This really should be impossible, but you can construct an 'unexpected error' here.")
}
}
}.resume()
}
There's a lots of ways to do this; for another, see this AltConf talk.

Swift is not printing or displaying name in App from a weather API?

if let jsonObj = jsonObj as? [String: Any],
let weatherDictionary = jsonObj["weather"] as? [String: Any],
let weather = weatherDictionary["description", default: "clear sky"] as?
NSDictionary {
print("weather")
DispatchQueue.main.async {
self.conditionsLabel.text = "\(weather)"
}
}
// to display weather conditions in "name" from Open Weather
"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}]
//No errors, but code is not printing or displaying in App.
I'm not sure how to help with your exact question unless you can provide some more code for context. However,
You might try using the built-in decoding that comes with Swift 4. Check it out here. Basically, you make a class that models the response object, like this:
struct Weather: Decodable {
var id: Int
var main: String
var description: String
var icon: String
}
Then decode it like so:
let decoder = JSONDecoder()
let weather = try decoder.decode(Weather.self, from: jsonObj)
And it magically decodes into the data you need! Let me know if that doesn't work, and comment if you have more code context for your problem that I can help with.
I put the complete demo here to show how to send a HTTP request and parse the JSON response.
Note, Configure ATS if you use HTTP request, rather than HTTPS request.
The demo URL is "http://samples.openweathermap.org/data/2.5/forecast?q=M%C3%BCnchen,DE&appid=b6907d289e10d714a6e88b30761fae22".
The JSON format is as below, and the demo shows how to get the city name.
{
cod: "200",
message: 0.0032,
cnt: 36,
list: [...],
city: {
id: 6940463,
name: "Altstadt",
coord: {
lat: 48.137,
lon: 11.5752
},
country: "none"
}
}
The complete demo is as below. It shows how to use URLSessionDataTask and JSONSerialization.
class WeatherManager {
static func sendRequest() {
guard let url = URL(string: "http://samples.openweathermap.org/data/2.5/forecast?q=M%C3%BCnchen,DE&appid=b6907d289e10d714a6e88b30761fae22") else {
return
}
// init dataTask
let dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
let name = WeatherManager.cityName(fromWeatherData: data)
print(name ?? "")
}
// send the request
dataTask.resume()
}
private static func cityName(fromWeatherData data: Data?) -> String? {
guard let data = data else {
print("data is nil")
return nil
}
do {
// convert Data to JSON object
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
print(jsonObject)
if let jsonObject = jsonObject as? [String: Any],
let cityDic = jsonObject["city"] as? [String: Any],
let name = cityDic["name"] as? String {
return name
} else {
return nil
}
} catch {
print("failed to get json object")
return nil
}
}
}