Swift - How do I show Data in Swift View from Data.swift file? - ios

In a separate data file called data.swift I have this code
struct Response: Decodable {
var data: Data
}
struct Data: Decodable {
var search: search
}
struct search: Decodable {
var __Typename: String
var query: String
var searchResults: searchResults
}
...and so on and so forth. I then decode the data from a Rapid-Api like so
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error!)
} else {
let products = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
if let water = products {
print("JSON: \n" + String(describing: water) + "\n")
}
}
})
How do I display the data elements in ProductList.swift it's a (view) file. The API works as expected and displays the JSON in the terminal. I am using XCODE 12.4 as I am not permitted to upgrade any further.

So, actually you want to receive the data in Model and show in view, either a label or image.
In your productSwift.List:
var response: Response?
Right now, you have to decode the data in the Model:
static func postApiCall<T: Decodable>(completion: #escaping (Result<T,Error>) -> Void) {
let url = URL(string: "Enter Your URL here")
let request = URLRequest(url: url!)
let dataTask = URLSession.shared.dataTask(with: request) { data, response , error in
guard let data = data else {
if error == nil {
completion(.failure(error as! Error))
}
return
}
do {
let decoder = JSONDecoder()
let json = try decoder.decode(T.self, from: data)
completion(.success(json))
} catch let error {
print(error.localizedDescription)
}
}
dataTask.resume()
}
}
Now in your ProductList.swift:
ServiceManage.postApiCall { (result : Result<Response,Error>) in
switch result {
case .success(let result):
print("result is \(result)")
self.response = response.data
self.yourLabel.text = response.data.search.query
case .failure(let failure):
print(failure)
}
}
and as Larme said, change your Struct "Data" name to something else.

Related

How can i make my Networking class generic in Swift?

Here i am extracting data as DataModel. But i want to make this class generic and pass the model myself so that i can use it to parse data from multiple API's. Can Anyone Help?
import Foundation
struct NetworkManager {
func fetchData(url : String, completion : #escaping (DataModel?) -> ()) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
}
else {
debugPrint("failed to fetch data")
completion(nil)
}
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}
With the convenient Result type you can write a quite tiny generic method, it returns the decoded type on success and any error on failure
func fetchData<T: Decodable>(urlString: String, completion: #escaping (Result<T,Error>) -> Void) {
guard let url = URL(string: urlString) else { return } // or throw an error
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(T.self, from: data!) })
}.resume()
}
Note: Force unwrapping data! is 100% safe if no error occurs
Be aware that you have to specify the concrete type when you are going to call the method
fetchData(urlString: "https://example.com/api") { (result : Result<MyModel,Error>) in
switch result {
case .success(let model): print(model)
case .failure(let error): print(error)
}
}
You can add a generic type constraint (called Model) which conforms Decodable like below:
struct NetworkManager {
func fetchData<Model: Decodable>(url : String, completion : #escaping (Model?) -> ()) {
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let decodedData = try JSONDecoder().decode(Model.self, from: safeData)
completion(decodedData)
} catch {
print("error while parsing data \(error)")
}
} else {
debugPrint("failed to fetch data")
completion(nil)
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
}
Usage
struct SampleModel: Decodable {
let name: String
}
NetworkManager().fetchData(url: "") { (data: SampleModel?) in
print(data)
}
You can write a generic function to fetch data like this one :
func fetchGenericData<T: Decodable>(urlString: String, completion: #escaping (T) -> ()) {
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, resp, err) in
if let err = err {
print("Failed to fetch data:", err)
return
}
guard let data = data else { return }
do {
let obj = try JSONDecoder().decode(T.self, from: data)
completion(obj)
} catch let jsonErr {
print("Failed to decode json:", jsonErr)
}
}.resume()
}
}
I suppose that you have a data model, if you have not, you should create for your every object. Also by using a dummy URL i will make a request and fetch the JSON includes some users name and ids with JSON format.
Let`s define a data model for this:
struct StackUser: Decodable {
let id: Int
let name: String
}
fetchGenericData(urlString: "https://api.stackoverexample.com/stackusers") { (stackUsers: [StackUser]) in
stackUsers.forEach({print($0.name)})
}
Finally you will be parse the data and prints like this:
Rob
Matt
Vadian

Store JSON data as a variable

I'm trying to figure out how to store JSON data into a variable for later use. How do I store it, and is it possible to use the variable in another view controller, or do I have to do another request to fetch the data?
This is my code:
#IBAction func signinTapped(_ sender: UIButton) {
guard let url = URL(string: "http://XXXXXX/TestReqIOS.php") else {
return
}
let email = txtEmail.text!
let password = txtPassword.text!
let data : Data = "loginSubmit=1&email=\(email)&password=\(password)&grant_type=password".data(using: .utf8)!
var request : URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type");
request.setValue(NSLocalizedString("lang", comment: ""), forHTTPHeaderField:"Accept-Language");
request.httpBody = data
print("Calling API")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// vs let session = URLSession.shared
// make the request
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
if let error = error {
print("error")
}
else if let response = response {
print("response")
}
else if let data = data {
print(data)
}
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
print(String(data: responseData, encoding: String.Encoding.utf8) ?? "")
}
})
task.resume()
}
Which will return:
{
"id": "7",
"first_name": "John",
"last_name": "Doe",
"email": "JohnDoe#text.com",
"created": "2019-03-11",
"modified": "2019-03-10",
}
It would be better to use a struct, such as in your case:
struct Data: Codable {
let id: Int
let first_name: String
let last_name: String
let email: String
let created: Date
let modified: Date
}
Then you create a variable of that struct where you will store it:
var dataVariable = [Data]()
Then you can do your URL call like:
func getData(arr: Bool, completion: #escaping (Bool) -> ()) {
let urlJSON = "URL"
guard let url = URL(string: urlJSON) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let getData = try JSONDecoder().decode([Data].self, from: data)
self.dataVariable = getData
} catch let jsonErr {
print("error serializing json: \(jsonErr)")
}
completion(arr)
}.resume()
}
Then you can access all of this from the dataVariable var. IF you do this in a Manager class you can access it from any ViewController.
To access:
let firstNameString = dataVariable[0].first_name
If there are not multiple trees of the same, then just make sure its:
let getData = try JSONDecoder().decode(Data.self, from: data)
Edit:
In your case put the above here:
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
if let error = error {
print("error")
}
else if let response = response {
print("response")
}
else if let data = data {
let getData = try JSONDecoder().decode([Data].self, from: data)
self.dataVariable = getData // <- Just decode here
print(data)
}
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
print(String(data: responseData, encoding: String.Encoding.utf8) ?? "")
}
})
task.resume()

Parsing JSON response using Codable gives error in swift

I'm trying to parse JSON response using Codable but it gives me error.
I tried to refer from the below stack overflow link but did not work.
how do I parse Any in dictionary using swift
Below is my code, not sure where I'm wrong in this.
> enum JSONError: String,Error {
> case NoData = "ERROR: no data"
> case ConversionFailed = "ERROR: conversion from JSON failed"
> }
struct Owner : Decodable {
let full_name : String
let html_url:String
let follower:follower
}
struct follower : Decodable {
let followers_url : String
}
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
let jsonResponse = try JSONSerialization.jsonObject(with:
data)
let entries = try! JSONDecoder().decode([Owner].self, from: jsonResponse as! Data)
print(jsonResponse)
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
I need to get 3 information from this response - full name, html url and followers.
Link for web api
https://api.github.com/search/repositories?q=language:ruby
Please latest have a look at the code.
Below is the error message :
'__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580). 2019-02-09
16:17:42.062971+0530 PhotoViewwer[13342:259997] Could not cast value
of type '__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580).
Thanks
Please learn to read JSON. It's pretty easy. There are only two collection types, array ([]) and dictionary ({})
Your structs are wrong.
In the root dictionary of the JSON there is an array of dictionaries for key items.
In each dictionary there are keys full_name and owner (here is the location of the Owner struct).
A dictionary for key follower does not exist.
These structs represent the JSON correctly
struct Response : Decodable {
let items : [Item]
}
struct Item : Decodable {
let fullName : String
let owner : Owner
}
struct Owner : Decodable {
let htmlUrl : URL // URL strings can be decoded directly into URL
let followersUrl : URL
}
Add a completion handler to your function and use an enum as result type. The failure case returns all real errors. An URLRequest is redundant. Just pass the URL. The JSONSerialization line is pointless.
The convertFromSnakeCase strategy converts snake_cased keys to camelCased struct members
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let entries = try decoder.decode(Response.self, from: data!)
completion(.success(entries))
} catch {
completion(.failure(error))
}
}.resume()
}
And call it
jsonParser { result in
switch result {
case .success(let entries) : print(entries)
case .failure(let error) : print(error)
}
}
Basically never use NS... classes if there are native equivalents, here URL for NSURL and URLRequest for NS(Mutable)URLRequest
Edit:
In Swift 5 the syntax becomes more convenient using the native Result type. It is able to convert the throwing expression
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result<Response,Error>) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
completion(Result{ try decoder.decode(Response.self, from: data!) })
}.resume()
}
You need
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let entries = try dec.decode(Root.self, from:data!)
print(entries)
} catch {
print(error)
}
}.resume()
}
struct Root : Decodable {
let items:[Item]
}
struct Owner: Codable {
let login: String
let id: Int
let nodeId: String
let avatarUrl: String
let gravatarId: String
let url, htmlUrl, followersUrl: String
let followingUrl, gistsUrl, starredUrl: String
let subscriptionsUrl, organizationsUrl, reposUrl: String
let eventsUrl: String
}
struct Item: Codable {
let fullName : String
let htmlUrl:String
let owner: Owner
}
You shouldn't cast the response to data here
from: jsonResponse as! Data)
as it will crash

Parse image from web json

I have a json file that looks something like this:
{
"adTitle": "My Title",
"adURL": "https://mylink.com/",
"adImageURL": "http://mywebsite/bannerx#3x.png"
}
I get the JSON value from website: http://mywebsite.com/file.json
The problem is that the ad somehow doesn't load the adImageURL, so when I press the UIImageView, but when I press the area that then UIImageView should be, it open my adURL. This is the code I use for JSON:
var imageURL:String = "http://mywebsite/bannerx#3x.png"
var adURL:String = "https://mylink.com/"
func loadAdvertisement() {
// Set up the URL request
let todoEndpoint: String = "http://mywebsite.com/file.json"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
// print("error calling GET on /todos/1")
// print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard (try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: AnyObject]) != nil else {
print("error trying to convert data to JSON")
return
}
let json = try JSONSerialization.jsonObject(with: responseData, options:.allowFragments) as! [String:AnyObject]
if (json != nil) {
self.imageURL = (json["adImageURL"] as? String)!
self.adURL = (json["adURL"] as? String)!
print(self.imageURL)
print(self.adURL)
DispatchQueue.main.async { () -> Void in
self.loadAdImage(self.imageURL)
}
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
// let jsonURL = URL(string: "http://mywebsite.com/file.json")
// self.getDataFromUrl(jsonURL!, completion: (data:Data?, response:URLResponse?, error:Error?)) -> Void
}
func loadAdImage(_ url:String) {
getDataFromUrl(URL(string: url)!) { (data, response, error) in
DispatchQueue.main.async { () -> Void in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? "")
print("Download Finished")
self.advertImageView.image = UIImage(data: data)
}
}
}
func getDataFromUrl(_ url:URL, completion: #escaping ((_ data: Data?, _ response: URLResponse?, _ error: NSError? ) -> Void)) {
URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?) in
completion(data, response, error as NSError?)
}.resume()
}
In the event LOG, is prints out both of the print("error trying to convert data to JSON") commands. I have used this code before in my project, and it worked just fine, but I have no idea why it wont work anymore.
Add the message to catch and check what actually error you are getting like this way:
do {
let json = try JSONSerialization.jsonObject(with: responseData, options:.allowFragments) as! [String:AnyObject]
} catch let message {
print("error trying to convert data to JSON" + "\(message)")
return
}

Swift 4 send POST request as x-www-form-urlencoded

I want to send a POST request to my php 7 server which accepts data as application/x-www-form-urlencoded. The data I have is inside a Struct and I want to get every property of this struct as a parameter when I submit it.
This is the struct which handles my urlSession requests both GET and POST
XHR.swift
struct XHR {
enum Result<T> {
case success(T)
case failure(Error)
}
func urlSession<T>(method: String? = nil, file: String, data: Data? = nil, completionHandler: #escaping (Result<T>) -> Void) where T: Codable {
let file = file.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
// Set up the URL request
guard let url = URL.init(string: file) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
if method == "POST" {
urlRequest.httpMethod = "POST";
urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
urlRequest.httpBody = data
print(urlRequest.httpBody)
}
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// vs let session = URLSession.shared
// make the request
let task = session.dataTask(with: urlRequest, completionHandler: {
(data, response, error) in
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
let decoder = JSONDecoder()
print(String(data: responseData, encoding: .utf8))
do {
let todo = try decoder.decode(T.self, from: responseData)
completionHandler(.success(todo))
} catch {
print("error trying to convert data to JSON")
//print(error)
completionHandler(.failure(error))
}
}
})
task.resume()
}
}
This is the functions which sends a POST request to the server:
VideoViewModel.swift
struct User: Codable {
let username: String
let password: String
static func archive(w:User) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<User>.stride)
}
static func unarchive(d:Data) -> User {
guard d.count == MemoryLayout<User>.stride else {
fatalError("BOOM!")
}
var w:User?
d.withUnsafeBytes({(bytes: UnsafePointer<User>)->Void in
w = UnsafePointer<User>(bytes).pointee
})
return w!
}
}
enum Login {
case success(User)
case failure(Error)
}
func login(username: String, password: String, completionHandler: #escaping (Login) -> Void) {
let thing = User(username: username, password: password)
let dataThing = User.archive(w: thing)
xhr.urlSession(method: "POST", file: "https://kida.al/login_register/", data: dataThing) { (result: XHR.Result<User>) in
switch result {
case .failure(let error):
completionHandler(.failure(error))
case .success(let user):
//let convertedThing = User.unarchive(d: user)
completionHandler(.success(user))
}
}
}
And I call it like this:
videoViewModel.login(username: "rexhin", password: "bonbon") { (result: VideoViewModel.Login) in
switch result {
case .failure(let error):
print("error")
case .success(let user):
print(user)
}
}
From PHP I can see that a POST request is submitted successfully but when I try to get the username field by doing $_POST["username"] I get Undefined index:
Full code of the app can be seen here https://gitlab.com/rexhin/ios-kida.git
I used below code in swift 4
guard let url = URL(string: "http://192.168.88.129:81/authenticate") else {
return
}
let user1 = username.text!
let pass = passwordfield.text!
print(user1)
print(pass)
let data : Data = "username=\(user1)&password=\(pass)&grant_type=password".data(using: .utf8)!
var request : URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type");
request.setValue(NSLocalizedString("lang", comment: ""), forHTTPHeaderField:"Accept-Language");
request.httpBody = data
print("one called")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// vs let session = URLSession.shared
// make the request
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
if let error = error
{
print(error)
}
else if let response = response {
print("her in resposne")
}else if let data = data
{
print("here in data")
print(data)
}
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
let decoder = JSONDecoder()
print(String(data: responseData, encoding: .utf8))
do {
// let todo = try decoder.decode(T.self, from: responseData)
// NSAssertionHandler(.success(todo))
} catch {
print("error trying to convert data to JSON")
//print(error)
// NSAssertionHandler(.failure(error))
}
}
})
task.resume()
}
You are passing the result of User.archive(w: thing) as the data embedded in the request body, which may never work. Generally, your archive(w:) and unarchive(d:) would never generate any useful results and you should better remove them immediately.
If you want to pass parameters where x-www-form-urlencoded is needed, you need to create a URL-query-like string.
Try something like this:
func login(username: String, password: String, completionHandler: #escaping (Login) -> Void) {
let dataThing = "username=\(username)&password=\(password)".data(using: .utf8)
xhr.urlSession(method: "POST", file: "https://kida.al/login_register/", data: dataThing) { (result: XHR.Result<User>) in
//...
}
}
The example above is a little bit too simplified, that you may need to escape username and/or password before embedding it in a string, when they can contain some special characters. You can find many articles on the web about it.
Another way of doing this is as follows:
Add the URLEncodedFormEncoder.swift into your project. This is a custom URLEncodedFormEncoder from Alamofire / Vapor.
Conform your model to native Swift Encodable protocol, just as you do with JSON coding.
Encode the model just as you do during json encoding
// example
let requstModel = OpenIDCTokenRequest(
clientId: clientId,
clientSecret: clientSecret,
username: username,
password: password
)
guard let requestData: Data = try? URLEncodedFormEncoder().encode(requstModel) else {
return // handle encoding error
}
Quoting from this post
In PHP, a variable or array element which has never been set is
different from one whose value is null; attempting to access such an
unset value is a runtime error.
The Undefined index error occurs when you try to access an unset variable or an array element. You should use function isset inorder to safely access the username param from the POST body. Try the below code in your PHP file.
if (isset($_POST["username"]))
{
$user= $_POST["username"];
echo 'Your Username is ' . $user;
}
else
{
$user = null;
echo "No user name found";
}

Resources