REST API call using Swift to recreate App Store - ios

So I have two models, one called AppCategory and App. I've created a function within the AppCategory class that makes an API call that brings me back all categories successfully and within those categories there is an array of dictionaries of apps which I get back in form of dictionaries but i'm not sure on how to set it as an App object.
Here is my AppCategory class:
import UIKit
class AppCategory {
var name: String?
var apps = [App]()
var type: String?
init(dictionary: [String: Any]) {
name = dictionary["name"] as? String
apps = dictionary["apps"] as! [App]
type = dictionary["type"] as? String
}
static func fetchFeaturedApps(completion: #escaping ([AppCategory]) -> Void) {
guard let url = URL(string: "https://api.letsbuildthatapp.com/appstore/featured") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
return
}
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
var appCategories = [AppCategory]()
for dict in json["categories"] as! [[String: Any]] {
let appCategory = AppCategory(dictionary: dict)
appCategories.append(appCategory)
if dict.index(forKey: "apps") != nil {
}
}
print(appCategories)
DispatchQueue.main.async {
completion(appCategories)
}
} catch {
print("Error in JSON Serialization")
}
}.resume()
}
}
Here is the App model class:
import Foundation
class App {
var id: Int?
var name: String?
var category: String?
var price: Double?
var imageName: String?
init(dictionary: [String: Any]) {
id = dictionary["Id"] as? Int
name = dictionary["Name"] as? String
category = dictionary["Category"] as? String
price = dictionary["Price"] as? Double
imageName = dictionary["ImageName"] as? String
}
}
I've done research and I could make these classes of type NSObject and use the setValue(forKey:) but I don't really want to do that. I've gotten as far as what's highlighted in the AppCategory class by saying if dict.index(forKey: "apps") != nil... Don't know if i'm on the right track but maybe someone can help me with this.

Related

JSON Parsing Swift using JSONPlaceholder

I'm playing with Swift and JSONPlaceholder. I want to retrieve all the data contained in: https://jsonplaceholder.typicode.com/photos
I created a function that is acceding to the url, downloading the JSON but then I don't know how can I obtain the title and the thumbnailUrl to pass then for populate the tableView. In the past I used this code but now it's not working because on the JSONPlaceholder there are no array.
Any help for re-arrange the code for read and obtain the jsonplaceholder elements?
func loadList(){
let url = URL(string: urlReceived)
var myNews = NewInfo()
let task = URLSession.shared.dataTask(with: url!) {
(data, response, error) in
if error != nil{
print("ERROR")
}
else{
do {
if let content = data{
let myJson = try JSONSerialization.jsonObject(with: content, options: .mutableContainers)
//print(myJson)
if let jsonData = myJson as? [String : Any] {
if let myResults = jsonData["list"] as? [[String : Any]]{
//dump(myResults)
for value in myResults{
//Read time string from root
if let time = value ["dt_txt"] as? String{
myNews.time = time
}
//Read main container
if let main = value["main"]
as? [String : Any]{
if let temperature = main["temp"] as? Double {
myNews.temperature = String(temperature)
}
}
//Read from weather container
if let weather = value["weather"] as? [[String: Any]]{
for value in weather{
if let weatherContent = value["description"] as? String{
myNews.weatherDescription = weatherContent
}
}
}
self.myTableViewDataSource.append(myNews)
}
dump(self.myTableViewDataSource)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
catch{
}
}
}
task.resume()
}//End func
Okey, so with Alamofire + SwiftyJSON, you can do this:
func loadList(){
let url = "https://jsonplaceholder.typicode.com/photos"
AF.request(url).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
print(json)
for value in json.arrayValue {
let url = value.dictionaryValue["url"]!.stringValue
let albumId = value.dictionaryValue["albumId"]!.stringValue
let thumbnailUrl = value.dictionaryValue["thumbnailUrl"]!.stringValue
let id = value.dictionaryValue["id"]!.stringValue
let title = value.dictionaryValue["title"]!.stringValue
// Add this album to array.
let album = AlbumModel(id: id, albumId: albumId, title: title, thumbnailUrl: thumbnailUrl)
albums.append(album)
}
case .failure(let error):
print(error)
}
}
}
EDIT:
I made model for values
class AlbumModel {
var id: String?
var albumId: String?
var title: String?
var thumbnailUrl: String?
init(id: String?, albumId: String?, title: String?, thumbnailUrl: String?){
self.id = id
self.albumId = albumId
self.title = title
self.thumbnailUrl = thumbnailUrl
}
}
After that, just create an array like var albums = [AlbumModel]() and you can append all the albums to this. Easy to use after in tableViewcell (example: albums[indexPath.row].id)

Get Firebase document objects as Swift object

I try to implement a simple shopping list swift application for iOS as a personal project. I did follow a guide for iOS on youtube.
My question is how do I parse the Item object from firebase to my ShoppingListItem swift object? If I execute the following code, it doesn't show any error message but it does not show any results either. If I uncomment all "items" lines, it shows the expected results without the item information.
Here is a screenshot from the firebase console of my firebase firestore structure / example object
Thanks in advance!
ShoppingListItem.swift
import Foundation
import FirebaseFirestore
protocol DocumentSerializable {
init?(dictionary: [String: Any])
}
struct ShoppingListItem {
var shoppingItemID: String
var priority: Int
var quantity: Int
var item: Item
var dictionary: [String: Any] {
return [
"shoppingItemID": shoppingItemID,
"priority": priority,
"quantity": quantity,
"item": item,
]
}
}
extension ShoppingListItem: DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let shoppingItemID = dictionary["shoppingItemID"] as? String,
let priority = dictionary["priority"] as? Int,
let quantity = dictionary["quantity"] as? Int,
let item = dictionary["item"] as? Item
else { return nil }
self.init(shoppingItemID: shoppingItemID, priority: priority, quantity: quantity, item: item)
}
}
struct Item {
var itemID: String
var lastPurchase: String
var name: String
var note: String
var picturePath: String
var dictionary: [String: Any] {
return [
"itemID": itemID,
"lastPurchase": lastPurchase,
"name": name,
"note": note,
"picturePath": picturePath,
]
}
}
extension Item: DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let itemID = dictionary["itemID"] as? String,
let lastPurchase = dictionary["lastPurchase"] as? String,
let name = dictionary["name"] as? String,
let note = dictionary["note"] as? String,
let picturePath = dictionary["picturePath"] as? String else { return nil }
self.init(itemID: itemID, lastPurchase: lastPurchase, name: name, note: note, picturePath: picturePath)
}
}
Get Data call in TableViewController.swift
db.collection("shoppingList").getDocuments(){
querySnapshot, error in
if let error = error {
print("error loading documents \(error.localizedDescription)")
} else{
self.shoppingArray = querySnapshot!.documents.flatMap({ShoppingListItem(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
I used the Codable protocol.
I used this as an extension to the Encodable Protocol:
extension Encodable {
/// Returns a JSON dictionary, with choice of minimal information
func getDictionary() -> [String: Any]? {
let encoder = JSONEncoder()
guard let data = try? encoder.encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any]
}
}
}
Then I use this to decode:
extension Decodable {
/// Initialize from JSON Dictionary. Return nil on failure
init?(dictionary value: [String:Any]){
guard JSONSerialization.isValidJSONObject(value) else { return nil }
guard let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []) else { return nil }
guard let newValue = try? JSONDecoder().decode(Self.self, from: jsonData) else { return nil }
self = newValue
}
}
Make your two structs conform to Codable (Item first, then ShoppingListItem). Of course, this may not work for the existing data stored in Firestore. I would first put data into Firestore via the getDictionary() (in a new collection), then try to read it back into your tableView.
You may also want to print the actual error when trying to Decode your data, this will greatly help you pinpoint the data error if there's any.
extension Decodable {
/// Initialize from JSON Dictionary. Return nil on failure
init?(dictionary value: [String:Any]) {
guard JSONSerialization.isValidJSONObject(value) else {
return nil
}
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
let newValue = try JSONDecoder().decode(Self.self, from: jsonData)
self = newValue
}
catch {
log.error("failed to serialize data: \(error)")
return nil
}
}
}

Access array of dictionaries Swift 3

I need to access the work data which is inside an array of dictionaries and I'm a little bit confuse with this. I'm using swift 3. Some one can give-me some piece of coding to make it done?
I'm using this
let work: NSArray! = fbData.value(forKey: "work") as! NSArray
if let position: NSArray = work[0] as! NSArray {
let positionName: String = position.value(forKey: "name") as! String
self.userWorkExpLabel.text = "\(positionName)" as String
}
but I'm having this answer:
Could not cast value of type '__NSDictionaryI' (0x1106c7288) to 'NSArray' (0x1106c6e28).
there's the API
{
"work": [
{
"employer": {
"id": "93643283467",
"name": "Oracast"
},
"location": {
"id": "111983945494775",
"name": "Calgary, Alberta"
},
"position": {
"id": "146883511988628",
"name": "Mobile Developer"
},
"start_date": "2017-04-30",
"id": "1446626725564198"
}
],
Ok guys. I tried what you posted and what I have now is something like this:
a structs class:
import Foundation
struct Worker{
let employer: Employer
let location: Location
let position: Position
let startDate:String
let id: String
init?(fromDict dict: Dictionary<String, Any>){
guard let employer = Employer(fromDict: dict["employer"] as? Dictionary<String, String>),
let location = Location(fromDict: dict["location"] as? Dictionary<String, String>),
let position = Position(fromDict: dict["position"] as? Dictionary<String, String>),
let startDate = dict["start_date"] as? String,
let id = dict["id"] as? String else {
return nil
}
self.employer = employer
self.location = location
self.position = position
self.startDate = startDate
self.id = id
}
}
struct Employer{
let id: String
let name: String
init?(fromDict dict:Dictionary<String, String>?){
guard let id = dict?["id"],
let name = dict?["name"] else{
return nil
}
self.id = id
self.name = name
}
}
struct Location {
let id:String
let name:String
init?(fromDict dict:Dictionary<String, String>?) {
guard let id = dict?["id"],
let name = dict?["name"] else {
return nil
}
self.id = id
self.name = name
}
}
struct Position {
let id:String
let name:String
init?(fromDict dict:Dictionary<String, String>?) {
guard let id = dict?["id"],
let name = dict?["name"] else {
return nil
}
self.id = id
self.name = name
}
}
Ive created a class called facebookGraphRequest.
import Foundation
import UIKit
import FBSDKCoreKit
import FBSDKLoginKit
import FBSDKShareKit
class facebookGraphRequest: NSObject {
class func graphRequestWork(completion: #escaping(_ error: Error?, _ facebookUserWork: Worker)-> Void){
if ((FBSDKAccessToken.current()) != nil){
let parameters = ["fields": "name, picture.width(198).height(198), location{location}, work{employer}, education, about, id"]
let graphRequest: FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: parameters)
graphRequest.start { (connection, result, error) in
if ((error) != nil ){
print(error!)
}else {
print(result!)
func workersArray(data:Dictionary<String, Any>)->[Worker]?{
guard let arrayOfDict = data["work"] as? Array<Dictionary<String, Any>> else {
return nil
}
return arrayOfDict.flatMap({ Worker(fromDict: $0)})
}
}
}
}
}
}
and I'm calling this data inside the viewController with:
func facebookLogin(){
facebookGraphRequest.graphRequestWork { (error: Error?, facebookUserWork: Worker) in
self.userNameJobPositionLabel.text = "\(facebookUserWork.position)"
self.companyNameLabel.text = "\(facebookUserWork.employer)"
}
}
Somebody knows what's happening? There's nothing happening with the labels.
I thought this apis was easier than that. I'm really confused with this process... Sorry if it looks like stupid questions but I'm really messing my mind because of this things... I really need your help guys. My work depends on that :(
After experimenting with Swift 4 and going in the direction that #PuneetSharma demonstrated I found it's even easier when you use raw JSON text, Codable, and JSONDecoder:
import Foundation
// define the nested structures
struct Work: Codable {
let work: [Worker]
}
struct Worker: Codable {
let employer: Employer
let location: Location
let position: Position
let startDate: String
let id: String
// needed a custom key for start_date
enum CodingKeys : String, CodingKey {
case employer, location, position, startDate = "start_date", id
}
}
struct Employer: Codable {
let id: String
let name: String
}
struct Location: Codable {
let id: String
let name: String
}
struct Position: Codable {
let id: String
let name: String
}
// turn the text into `Data` and then
// decode as the outermost structure
if let jsonData = json.data(using: .utf8),
let work = try? JSONDecoder().decode(Work.self, from: jsonData) {
print(work)
}
The result is a Work structure with all the data:
Work(work: [
Model.Worker(employer : Model.Employer(id : "93643283467",
name: "Oracast"),
location : Model.Location(id : "111983945494775",
name: "Calgary, Alberta"),
position : Model.Position(id : "146883511988628",
name: "Mobile Developer"),
startDate: "2017-04-30",
id : "1446626725564198")
])
(I formatted the output a bit to clarify the structures produced.)
You get a lot of functionality for free just by using Codable. It's also simple to go the other way and produce JSON text from any of the structures.
You should ideally introduce model classes like this:
struct Worker {
let employer:Employer
let location:Location
let position:Position
let startDate:String
let id:String
init?(fromDict dict:Dictionary<String, Any>) {
guard let employer = Employer(fromDict: dict["employer"] as? Dictionary<String, String>), let location = Location(fromDict: dict["location"] as? Dictionary<String, String>), let position = Position(fromDict: dict["position"] as? Dictionary<String, String>), let startDate = dict["start_date"] as? String, let id = dict["id"] as? String else {
return nil
}
self.employer = employer
self.location = location
self.position = position
self.startDate = startDate
self.id = id
}
}
struct Employer {
let id:String
let name:String
init?(fromDict dict:Dictionary<String, String>?) {
guard let id = dict?["id"], let name = dict?["name"] else {
return nil
}
self.id = id
self.name = name
}
}
struct Location {
let id:String
let name:String
init?(fromDict dict:Dictionary<String, String>?) {
guard let id = dict?["id"], let name = dict?["name"] else {
return nil
}
self.id = id
self.name = name
}
}
struct Position {
let id:String
let name:String
init?(fromDict dict:Dictionary<String, String>?) {
guard let id = dict?["id"], let name = dict?["name"] else {
return nil
}
self.id = id
self.name = name
}
}
Now, you can introduce a function like this:
func workersArray(data:Dictionary<String, Any>)->[Worker]?{
guard let arrayOfDict = data["work"] as? Array<Dictionary<String, Any>> else {
return nil
}
return arrayOfDict.flatMap({ Worker(fromDict: $0)})
}
Use this code
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let workArray = json["work"] as? [[String: Any]] {
if let dictWork = workArray.first {
if let dictPosition = dictWork["position"] as? [String: String] {
print("position name : \(dictPosition["name"])")
}
}
}
}

[<TestingAPI.Album 0x6100000ff300> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key albumId

I am new to iOS development and been trying to jump into swift straight away. I am trying to work with APIs and trying to learn myself. I've build this test Collection view along with the model to get the data however when I run the app I get a crash. Been trying to find a solution with no luck.
I've seen few that have the same crash however mostly due to a xib file which I am not using. I am building the app solely in code.
AlbumId
import UIKit
class AlbumId: NSObject {
var albumId: NSNumber?
static func fetchAlbums() {
let urlString = "https://jsonplaceholder.typicode.com/photos"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error ?? "")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
var albums = [Album]()
for dict in json as! [Any] {
let album = Album(dictionary: dict as! [String: Any])
album.setValuesForKeys(dict as! [String : Any])
albums.append(album)
}
} catch let err {
print(err)
}
}.resume()
}
}
Album
class Album: NSObject {
var id: NSNumber?
var title: String?
var url: String?
var thumbnailUrl: String?
init(dictionary: [String: Any]) {
super.init()
id = dictionary["id"] as? NSNumber
title = dictionary["title"] as? String
url = dictionary["url"] as? String
thumbnailUrl = dictionary["thumbnailUrl"] as? String
}
}
Your class Album does not have a property called albumId which means that it is not KVC compliant for that key.
It seems your JSON response has a key "albumId", but since your class is not KVC-compliant (it doesn't have an "albumId" property) using setValuesForKeys fails because setValuesForKeys requires that the instance must be KVC compliant for all of the keys in the dictionary.
Without a little knowledge regarding the JSON response, we can only make recommendations based on assumptions.
Your options are:
Change the property "id" to "albumId" on class Album
Change your API so the JSON key is simply "id"
Override setValueForKey and redirect "albumId" to your "id" property.
The error occurs because the model does not contain the property albumId.
Calling the KVC method setValuesForKeys is redundant anyway since you are initializing the object from the dictionary. There are only a few rare cases in Swift where KVC is useful. This is none of them. And inheritance from NSObject is actually not needed either.
The received JSON has id and albumId keys, so add the latter to the model and use Int rather than NSNumber. This code uses non-optional constants (let) with default values empty string / 0
class Album {
let albumId : Int
let id: Int
let title: String
let url: String
let thumbnailUrl: String
init(dictionary: [String: Any]) {
albumId = dictionary["albumId"] as? Int ?? 0
id = dictionary["id"] as? Int ?? 0
title = dictionary["title"] as? String ?? ""
url = dictionary["url"] as? String ?? ""
thumbnailUrl = dictionary["thumbnailUrl"] as? String ?? ""
}
}
Now populate the albums array, as always .mutableContainers is completely meaningless in Swift
if let json = try JSONSerialization.jsonObject(with: data!) as? [[String: Any]] else { return }
var albums = [Album]()
for dict in json {
let album = Album(dictionary: dict)
albums.append(album)
}
or in a swiftier way
var albums = json.map { Album(dictionary: $0) }

How to make images appear in the view with its labels in UICollectionView cells using Alamofire?

I'm making an app like the AppStore with UICollectionView so when downloading the data from url
import Alamofire
import AlamofireImage
class AppCategory: NSObject {
var id: NSNumber?
var title: String?
var apps: [App]?
var type: NSNumber?
static func fetchFeaturedApps(completionHandler: #escaping ([AppCategory]) -> ()) {
let jsonResult = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
var appCategories = [AppCategory]()
let sections = OFFERIM["sections"] as! [Dictionary<String, AnyObject>]
for i in 0..<sections.count {
var appCategory = AppCategory()
var apps = [App]()
let sectionTitle = sections[i]["title"] as! String
appCategory.title = sectionTitle
let sectionType = sections[i]["type"] as! NSNumber
appCategory.type = sectionType
let sectionId = sections[i]["id"] as! NSNumber
appCategory.id = sectionId
// for Apps inside each section
let lists = sections[i]["lists"] as! [String: AnyObject]
for j in 0..<lists.count{
let app = App()
let id = lists[j]["id"] as! NSNumber
app.id = id
let thumb = lists[j]["thumb"]! as! String
Alamofire.request(thumb).responseImage { response in
if let image = response.result.value {
app.image = image
print("image downloaded: \(image)")
}
}
let title = lists[j]["title"] as! String
app.title = title
apps.append(app)
}
appCategory.apps = apps
appCategories.append(appCategory)
}
DispatchQueue.main.async(execute: {
completionHandler(appCategories)
})
}catch{
print("JSON Processing Failed")
}
}.resume()
}
}
and I have these classes as models
class App: NSObject {
var id: NSNumber?
var title: String?
var image: UIImage?
}
and I call featuredApps in the viewDidLoad in viewController
what happened is when the view loaded the labels appears but the images not in the collection view .
I need to scroll each row to make the images appear.
and I don't kwon the problem where ???
Keep the url and set the url to image view use AlamofireImage.
imageView.af_setImage(withURL: url)

Resources