Parse JSON Result To UILabel in Swift - ios

I'm really new into swift & currently learning API by doing a project that shows list of games from rawg.io referring to the website's doc. I created GameFeed.swift & GameDetail.swift to pull name, release date, and rating from it and working fine in my console.
GameFeed.swift :
struct GameFeed: Codable {
let results:[GameDetail]
}
GameDetail.swift :
struct GameDetail: Codable {
let name:String
let released:String
let rating:Double
}
Now i'm trying to put the results to a simple UIlabel like gameName.text, gameReleased.text & gameRating.text from ViewController.swift so it will be show in Main.Storyboard
i did research on google about how to show it to these UIlabel by using DispatchQueue.main.async but when i'm declaring it, it receiving error :
Value of type 'GameFeed' has no member 'name'
same error messages also happened to released & rating. This is my ViewController.Swift :
class ViewController: UIViewController {
#IBOutlet weak var gameName: UILabel!
#IBOutlet weak var gameReleased: UILabel!
#IBOutlet weak var gameRating: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Defining API Site
let urlString = "https://api.rawg.io/api/games"
let url = URL(string: urlString)
guard url != nil else {
return
}
// Calling API
let session = URLSession.shared
let dataTask = session.dataTask(with: url!){
(data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let gameFeed = try decoder.decode(GameFeed.self, from: data!)
print(gameFeed)
DispatchQueue.main.async {
self.gameName.text = gameFeed.name
self.gameReleased.text = gameFeed.released
self.gameRating.text = gameFeed.rating
}
}
catch {
print("Error Parsing JSON")
}
}
}
dataTask.resume()
}
}
What should i do to make it possible to parse the data to labels?

The GameFeed contains an Array of GameDetails. But you are trying to set a single GameDetail on those labels. You should first pull out a single GameDetail from that array, then assign it in a way you like.
DispatchQueue.main.async {
let gameDetail = gameFeed.results.first // <- This will return the first one
self.gameName.text = gameDetail?.name
self.gameReleased.text = gameDetail?.released
self.gameRating.text = gameDetail?.rating
}

Related

How to pass data from ViewModel to another ViewModel

I have two viewmodels, one contains the necessary data for the user, a classic api fetch, and the second viewmodel is also an api fetch, but I want to insert the data from the first into the second, whenever I try, it always outputs nil, although before that I ensure that it cannot output it because I know it exists because the fetch function was called first.
class UserPostViewModel: ObservableObject {
#Published var user: UserResponse?
#Published var phoneNumber:String = ""
#Published var firstName:String = ""
#Published var lastName:String = ""
#Published var selectedImage: UIImage?
#Published var normalizedMobileNumberField:String = ""
func createUser() {
guard let url = URL(string: "privateapi") else {
return
}
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let data = data, error == nil else{
return
}
do {
let response = try? JSONDecoder().decode(UserResponse.self, from: data)
if self.normalizedMobileNumberField != "The number you enter is invalid." {
DispatchQueue.main.async {
self.user = response
if let userData = self.user {
print(userData.data.id)
}
}
} else {
print("You can't register application")
}
}
catch {
print(error)
}
}
task.resume()
}
}
This is the second one:
class GetTokenViewModel: ObservableObject {
#Published var tokenData: DataToken?
var userVM = UserPostViewModel()
func fetchData() {
let urlString = "privateapi/\(userVM.user.token)"
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url:url)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
return
}
do {
let decodedData = try JSONDecoder().decode(ResponseToken.self, from: data)
DispatchQueue.main.async{
self.tokenData = decodedData.data
print("TOKEN: \(self.tokenData?.token)")
}
} catch {
print("Error decoding JSON in gettoken: \(error)")
}
}.resume()
}
}
How to pass data from First ViewModel to the second?
I know that these codes have some error but I deleted every private information, but I believe that there is minimal code for understanding my question.
In SwiftUI, the View struct is equivalent to a view model but it is an immutable value type instead of an object that helps prevent consistency bugs. The property wrappers help it behave like an object. You pass data down the View struct hierarchy either with let if you want read access or #Binding var if you want write access. SwiftUI will track that these lets/vars are read in body and then body will be called whenever changes are detected. Transform the data as you pass it along from complex model types to simple value types.
#State declares a source of truth and #StateObject is when you need a reference type in an #State because you are doing something asynchronous or need to work around the limitations of closures in structs. Because these states are sources of truth you can't pass anything in because they won't be able to detect changes the same way the View struct can with let or #Binding var.
The best way to call web API in SwiftUI now is .task and .task(id:). The task will start when the view appears, cancelled if it disappears, or restarted if the id changes. This can completely remove the need for an object.

How can I use a URL for an image in Swift?

I am trying to retrieve certain data from my Firebase Database - the profile image. As you can see, this is from a UITableViewCell. I have an #IBOutlet for my imageView I want to cover.
As the view awakens, you can see that I go through, and make sure that I can get the information. I know how to retrieve data from Firebase, but not photo URLs, and then convert to the photo itself.
I'm not sure why it isn't working. I am getting an error, and will show it below. There is a possibility it is because of the URL unwrapping stuff, or as if the Firebase isn't formatted correctly, which I think it is, though.
Error Message : Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
import UIKit
import FirebaseAuth
import FirebaseDatabase
import Firebase
class ProfileCellControler: UITableViewCell {
#IBOutlet var name : UILabel!
#IBOutlet var rating : UILabel!
#IBOutlet var imageViewPro : UIImageView!
var databaseRefer : DatabaseReference!
var databaseHandle : DatabaseHandle!
override func awakeFromNib() {
super.awakeFromNib()
var urlString = ""
let urll = URL(string: urlString)!
databaseRefer = Database.database().reference()
let userID = Auth.auth().currentUser!.uid
databaseHandle = databaseRefer.child("Users").child(userID).child("Profile").child("Profile Name").observe(.value, with: { (data) in
print(String((data.value as? String)!))
self.name.text = "\(String((data.value as? String)!))"
print("Done")
})
databaseHandle = databaseRefer.child("Users").child(userID).child("Profile").child("Stars").observe(.value, with: { (data) in
print(String((data.value as? String)!))
if ((String((data.value as? String)!)) == "N/A") {
self.rating.text = "No Rating"
} else {
self.rating.text = "\(String((data.value as? String)!)) ★"
}
print("Done")
})
databaseHandle = databaseRefer.child("Users").child(userID).child("Profile").child("Profile Image").observe(.value, with: { (data) in
print(String((data.value as? String)!))
print("Done \(String((data.value as? String)!))")
urlString = (String((data.value as? String)!))
})
ImageService.downloadImage(withURL: urll) { (image) in
self.imageViewPro.image = image
}
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
The string for the URL is found nil because you are creating the call to download the image for your url before the urll has been initialized with a value from the database in:
databaseHandle = databaseRefer.child("Users").child(userID).child("Profile").child("Profile Image").observe(.value, with: { (data) in
print(String((data.value as? String)!))
print("Done \(String((data.value as? String)!))")
urlString = (String((data.value as? String)!))
})
observe(.value, with: ) Is an asynchronous operation thus
ImageService.downloadImage(withURL: urll) { (image) in
self.imageViewPro.image = image
}
Is being called before observe(.value, with:) is resolved. I would recommend moving the callback for the download URL inside of the completion for .observe(:value, :with) or using grand central dispatch to control the flow better.
As a side note, I highly recommend SDWebImage for handling your image downloading needs as it is configurable with a default image for situations such as this when the image fails to load.
Import KingFisher to make your life easier and then..
Download string representation of image from Firebase asynchronically.
Assign downloaded image to imageView with .kf.setImage method.

How retrieve values from an object on Cloud Firestore? Swift

I'm quite new on Firestore and Firebase libraries. Can someone explain how could I retrieve the fields from my object and stored in a variable on my code? I'm trying to access all the latMin, latMax, lonMin, and lonMax from each object in my Firestore, as well if is a way to retrieve each field with the same name (ex. latMin from place 1 and 2). I don't know if this is possible or maybe there is a better way to have organized my Firebase.
Here is my Cloud Firebase: Here is the image for my Firestore, requested in the comments.
place 1: //This is an Object in my Firestore
//Field = Number : Value ----> This is how object is orginazed
latMax : 39.727
latMin : 39.726
lonMax : -121.7997
lonMin : -121.8003
place 2:
latMax : 39.7559
latMin : 39.755
lonMax : -122.1988
lonMin : -122.1992
I was able to access the objects without any problem, this is the function I'm using to read the documents on my Firestore:
import UIKit
import FirebaseFirestore
import Firebase
import CoreLocation
class SecondViewController: UIViewController, CLLocationManagerDelegate {
//Creating access to locationManager
var locationManager : CLLocationManager!
#IBOutlet weak var latLabel: UILabel!
#IBOutlet weak var lonLabel: UILabel!
#IBOutlet weak var place: UILabel!
#IBOutlet weak var placeImage: UIImageView!
//Storing the pass data that we got from the firt View
var lonStore = String()
var latStore = String()
var fireLon = String()
var fireLat = String()
var lonNumberStore = Double()
var latNumberStore = Double()
var fireLonMax = Double()
var fireLatMax = Double()
var fireLonMin = Double()
var fireLatMin = Double()
override func viewDidLoad() {
//Here goes some code to display on the SecondViewController
readArray()
}
func readArray() {
let placeRef = Firestore.firestore().collection("places")
placeRef.getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error \(error!)")
return
}
for document in snapshot.documents {
let documentId = document.documentID
let latMax = document.get("latMax") as! String //Getting a nil error
print(documentId, latMax) //This print all objects
}
}
}
I'm missing something and I know it, the problem is that I can't find any reference of what I need to do in this case, I had read the documentation like 50 times and I can't find the solution. Thanks for taking the time to read my post.
Your readArray code is SUPER close. Just need to add code to read the individual fields within the document
func readArray()
self.db.collection("places").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let docId = document.documentID
let latMax = document.get("latMax") as! String
let latMin = document.get("latMin") as! String
let lonMax = document.get("lonMax") as! String
let lonMin = document.get("lonMin") as! String
print(docId, latMax, latMin, lonMax, lonMin)
}
}
}
The problem in the original question is the structure of the database. Here's a structure that will match the code in my answer and will be better suited for what the OP wants to accomplish.
The structure in the question has multiple places listed under one collection - that's OK but it groups the places together. If we change that to have one place per collection, it makes iterating over them and getting to the data much easier.
I think your places model have references to two objects bestBuy and house. So, the retrieval approach would be to retrieve the data from and store in the object only. Means you can directly set all the data in plcaes model. Then you can call getter methods of bestBuy and house to retrieve the data as you want. Here is a sampe code (here it is retrieving only one document) but you can apply the same for all documents by iterating a for loop and converting each document to object:-
let docRef = db.collection("cities").document("BJ")
docRef.getDocument { (document, error) in
if let city = document.flatMap({
$0.data().flatMap({ (data) in
return City(dictionary: data)
})
}) {
print("City: \(city)")
} else {
print("Document does not exist")
}
}
But according to firestore documentation the best way to store nested objects is creating a subcollection and then storing it in its document.
Some clarification since the iOS Firestore documentation is, IMO, light in many areas: there are two ways to retrieve the values from a document field (once the document has been gotten)--using a Firestore method (per the documentation) and using Swift's native method of getting values from dictionaries.
let query = Firestore.firestore().collection("zipCodes").whereField("california", arrayContains: 90210)
query.getDocuments { (snapshot, error) in
guard let snapshot = snapshot,
error == nil else {
return print("error: \(error!)")
}
guard !snapshot.isEmpty else {
return print("results: 0")
}
for doc in snapshot.documents {
// using firestore's convention
if let array = doc.get("zipCodes") as? [Int] {
if array.contains(90211) {
// doc contains both zip codes
}
}
// using swift's convention
if let array = doc.data()["zipCodes"] as? [Int] {
if array.contains(90211) {
// doc contains both zip codes
}
}
}
}
Personally, I would use as much of Firestore's framework as possible for safety and predictability and, therefore, use get() for all field retrieval.

iOS Swift & Parse - getDataInBackground not working?

Currently learning Swift & iOS. I try to access with Parse a saved picture. However, I can't access it with getDataInBackground(block:).
Here's my code:
//
// ViewController.swift
// Instragram
//
// Created by Macbook Pro on 22.07.17.
// Copyright © 2017 Macbook Pro. All rights reserved.
//
import UIKit
import Parse
class ViewController: UIViewController {
#IBOutlet weak var picture: UIImageView!
#IBOutlet weak var senderLbl: UILabel!
#IBOutlet weak var recieverLbl: UILabel!
#IBOutlet weak var messageLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Creating tables and data in database
let imageData = UIImageJPEGRepresentation(picture.image!, 0.5)
let file = PFFile(name: "picture.jpg", data: imageData!)
let table = PFObject(className: "messages")
table["sender"] = "Akhmed"
table["reciver"] = "Bob"
table["picture"] = file
table["message"] = "Hello!"
table.saveInBackground {(success, error) -> Void in
if(success){
print("Saved successful")
} else {
print(error!)
}
}
//Recieving Data from the Server
let information = PFQuery(className: "messages")
information.findObjectsInBackground{(objects: [PFObject]?, error) -> Void in
if error == nil {
for object in objects!{
self.senderLbl.text = object["sender"] as? String
self.recieverLbl.text = object["reciver"] as? String
self.messageLbl.text = object["message"] as? String
object["picture"].getDataInBackground(...)
}
} else {
print(error!)
}
}
}
}
Down after I access the name, receiver and message string I try to access an image that has been saved on there server with:
object["picture"].getDataInBackground(block:)
However, Swift won't even autocorrect anymore after I've typed object["picture"]. I get also an error:
'Value of type "Any" has no Member 'getDataInBackground(block:)'
Any ideas what's wrong? It seems to me that Swift can't find the string picture even though the image is saved on the server under the string "picture".
You need to first cast it as a PFFfile object and then retrieve the actual image data with getDataInBackground function like this:
let imageFile = object["picture"] as? PFFile
imageFile?.getDataInBackground (block: { (data, error) -> Void in
if error == nil {
if let imageData = data {
self.myImage = UIImage(data:imageData)
}
}
})

swift, parsed Json data append into an Array but can't access the Array from default viewController

I have two swift files in my project. One is called loadApi.swift and the other one is the default ViewController.swift
I intend to include all the functionalities of getting and parsing Json data in the loadApi.swift. Bellow is the code
import Foundation
var arr: Array = [String]()
func loadApi(){
var url = "http://api.wordnik.com:80/v4/word.json/love/definitions?limit=200&includeRelated=true&sourceDictionaries=ahd&useCanonical=true&includeTags=false&api_key=a2a73e7b926c924fad7001ca3111acd55af2ffabf50eb4ae5"
let session = NSURLSession.sharedSession()
let wordnik = NSURL(string: url)
//println(wordnik)
var task = session.dataTaskWithURL(wordnik!){
(data, response, error)-> Void in
if error != nil {
println("Internet error! Check your Internet Connection")
} else{
var error : NSError?
var vocabData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSArray
if error != nil{
println("Parse Json error!")
}else{
for items in vocabData{
let def = items["text"] as String
arr.append(def)
}
}
}
}
task.resume()
}
well, everything works fine and I can get and parse the Json data from the url and than append them into the "arr" Array. However, when I try to call the "arr" Array in the default ViewController.swift, I got this error saying "missing parameter #2 in call". Bellow is my code in the ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
loadApi()
println(arr)
//error saying "missing parameter #2 in call"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
How can I access the "arr" Array that contains all the strings I got from the Json data?
Changing the declaration of the array to var arr = [String]() should do the trick.

Resources