IGListKit insert data to class from Alamofire request - ios

So i have this model
class Event: NSObject {
var _eventName: String!
var _venueName : String!
var _eventImage: String!
var eventName: String {
if _eventName == nil {
_eventName = ""
}
return _eventName
}
var venueName: String {
if _venueName == nil {
_venueName = ""
}
return _venueName
}
var eventImage: String {
if _eventImage == nil {
_eventImage = ""
}
return _eventImage
}
init(eventsDict: Dictionary<String, AnyObject>) {
if let venue = eventsDict["venue"] as? Dictionary<String, AnyObject> {
if let venuname = venue["name"] as? String{
self._venueName = venuname
}
if let eventname = eventsDict["name"] as? String {
self._eventName = eventname
}
if let eventimage = eventsDict["coverPicture"] as? String {
self._eventImage = eventimage
}
}
}
And i make it IGListDiffable with this extension.
extension NSObject: IGListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
return isEqual(object)
}
}
So when I'm loading data from hardcoded code like this
var entries = [Event]()
func loadFakeEvents() {
let entries = [
Event(
eventName: "Ζωρζ Πιλαλι Και Η Soufra Band Στο AN Groundfloor - Live Stage!",
venueName: "AN Groundfloor - live stage",
eventImage: "https://scontent.xx.fbcdn.net/v/t31.0-8/s720x720/15936729_1867160333520142_8855370744955080264_o.jpg?oh=8198bc10a8ea61011d7ec1902b34aa01&oe=593D6BC4"
),
Event(
date: "2017-02-18T21:30:00+0200",
name: "Διονύσης Σαββόπουλος at Gazarte I Main Stage 18/02",
venuename: "Gazarte",
eventImage: "https://scontent.xx.fbcdn.net/v/t1.0-9/s720x720/16265335_1262826863809003_3636661375515976849_n.jpg?oh=5bb342321a65d33dbc1cc41de266b45e&oe=5907857C"
)
]
self.entries = entries
}
The events are loading fine. As they have to.
But when i'm making an alamofire request, of course, it takse some time to load the data and append them to the empty array of events.
This is the function that I have to call the events
func loadEvents() {
let parameters: Parameters = [
"Some" : "Parameters",
"Some" : "Parameters"
]
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
print(dict) // <-- Check this out
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
}
}
}
}
}
So in the above code i have a print, which prints the json.
And in my
extension LocationViewController: IGListAdapterDataSource {
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
let items: [IGListDiffable] = loader.entries as [IGListDiffable]
print(items.count) // <--- another print of items that should be displayed
return items
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return NormalSectionController()
}
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
}
Adapter i also print the items that should be displayed.
So when i load the fakeEvents function it prints 2 but when i load them with the normal function it prints 0 and then the JSON from the dict var from the previous code.
Normally i would reloadData() of the collection view.
But with IGListKit what is the trick of sending the Event Class to the CollectionView?
Thanks a lot for your time and i hope i'm not off topic !

Pasting my answer from this same issue on Github in case anyone finds this.
https://github.com/Instagram/IGListKit/issues/468
It looks like you're missing a call to self.adapter.performUpdates(animated: true) after the for-loop when appending to the entries dict:
func loadEvents() {
// ...
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if responseData.result.value != nil {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
// missing this!
self.adapter.performUpdates(animated: true)
// missing that!
}
}
}
}
}

Related

Get array from response in swift

i'm trying to get response in my model object, but facing an issue that it is showing me only first item of response, this is my code,
func getUserBalanceAPI()
{
APIService.getUserBalance{ (responseObject) in
if (responseObject?.status)! {
self.balanceArray.removeAll()
let user = UserCompleteBalance(JSON: (responseObject?.data as! [[String : Any]]).first!)
self.balanceArray.append(user!)
//Reload Collection View
self.currencyCVC.reloadData()
}
else if !(responseObject?.status)! {
Utilities.showBar(text: responseObject?.errorObject?.message)
}
}
}
How can i get all the items in an array? This is my response,
"responseBody": {
"data": [
{
"auction_deposit": 4083.63,
"currencyCode": "USD",
"userCurrencyId": 1,
"availableBalance": 64555.1,
"currentBalance": 68638.73
},
{
"auction_deposit": 0.0,
"currencyCode": "AED",
"userCurrencyId": 2,
"availableBalance": 198000.0,
"currentBalance": 198000.0
},
{
"auction_deposit": 0.0,
"currencyCode": "EUR",
"userCurrencyId": 3,
"availableBalance": 50000.0,
"currentBalance": 50000.0
}
]
}
This is my model class,
class UserCompleteBalance : Mappable {
var auctionDeposit : Int?
var availableBalance : Int?
var currencyCode : Int?
var currentBalance : Int?
var userCurrencyId : Int?
required init?(map: Map) {
}
func mapping(map: Map) {
auctionDeposit <- map["auction_deposit"]
currencyCode <- map["currencyCode"]
userCurrencyId <- map["userCurrencyId"]
availableBalance <- map["availableBalance"]
currentBalance <- map["currentBalance"]
}
}
Now i want to store all the response in this.
Your issue is related to the fact that you are getting only the first dictionary on the dictionary array, so you need to loop over your dictionary array to convert each of them on UserCompleteBalance model
Updated
You need to use [weak self] and strongSelf inside closure to avoid retain cycles
try with this code
func getUserBalanceAPI()
{
APIService.getUserBalance{ [weak self] (responseObject) in
guard let strongSelf = self else {
return
}
if (responseObject?.status)! {
strongSelf.balanceArray.removeAll()
if let usersDataArray = responseObject?.data as? [[String : Any]] {
for userData in usersDataArray {
strongSelf.balanceArray.append(UserCompleteBalance(JSON:userData))
}
}
//Reload Collection View
strongSelf.currencyCVC.reloadData()
}
else if !(responseObject?.status)! {
Utilities.showBar(text: responseObject?.errorObject?.message)
}
}
}
let user = UserCompleteBalance(JSON: (responseObject?.data as! [[String : Any]]).first!)
self.balanceArray.append(user!)
Above lines should be in a loop through all objects in responseObject.data
Your getUserBalanceAPI functions should be
APIService.getUserBalance{ (responseObject) in
if (responseObject?.status)! {
self.balanceArray.removeAll()
if let jsonObjects = responseObject?.data as? [[String : Any]] {
for jsonObject in jsonObjects {
if let user = UserCompleteBalance(JSON: jsonObject) {
self.balanceArray.append(user)
}
}
}
//Reload Collection View
self.currencyCVC.reloadData()
} else if !(responseObject?.status)! {
Utilities.showBar(text: responseObject?.errorObject?.message)
}
}
}

Call func with various struct

I want to create one func which i can used with various struct.
I have several struct and I want use one func with all my struct.
I work with Firestore and want use this one func to access the Firestore.
My first struct:
struct Profile {
var name = ""
var surname = ""
var email = ""
var dictionary: [String: Any] {
return [
"name": name,
"surname": surname,
"email": email
]
}
}
extension Profile: DocumentSerializable {
init?(dictionary: [String: Any], id: String) {
let name = dictionary["name"] as? String ?? ""
let surname = dictionary["surname"] as? String ?? ""
let email = dictionary["email"] as? String ?? ""
self.init(name: name,
surname: surname,
email: email)
}
}
My second struct:
struct FavoriteList {
var favoriteList: [String]
var id: String
var dictionary: [String: Any] {
return [
"favoriteList": favoriteList,
"id": id
]
}
}
extension FavoriteList: DocumentSerializable {
init?(dictionary: [String : Any], id: String) {
let favoriteList = dictionary["favorite"] as? [String] ?? [""]
let id = id
self.init(favoriteList: favoriteList, id: id)
}
}
And my func which I used now to load data from firestore:
func observeQuery() {
guard let query = query else { return }
let time = DispatchTime.now() + 0.5
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
if let snapshot = snapshot {
DispatchQueue.main.asyncAfter(deadline: time) {
let profileModels = snapshot.documents.map { (document) -> Profile in
if let profileModel = Profile(dictionary: document.data(), id: document.documentID) {
return profileModel
} else {
fatalError("Error!")
}
}
self.profile = profileModels
self.document = snapshot.documents
self.tableView.reloadData()
}
}
}
}
So how I can make func observeQuery to use my structs Profile or FavouriteList?
You can use Generic Functions :
func observeQuery<T>(someObject: T) {
if someObject is Profile {
//do something
} else if someObject is FavouriteList {
//do something
}
}

Why the first completionHandler return the data before all methods are called?

Here is my API code
import UIKit
import Alamofire
import SwiftyJSON
class ItunesApi: NSObject
{
var artistArray:Array <Artist> = []
func downloadData(name:String, _ completionHandler: #escaping (_ result: Array<Artist>) -> Void)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=musicArtist").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.findDiscography(data: JsonResult)
}
completionHandler(self.artistArray)
}
}
func findDiscography (data: JSON)
{
for (subJson):(String, JSON) in data["results"]
{
if let artistName = subJson.1["artistName"].string
{
print(artistName);
self.downloadDiscography(name: artistName)
}
}
}
func downloadDiscography (name: String)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=album").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.createDataModel(name: name, data: JsonResult)
}
}
}
func createDataModel (name: String, data: JSON)
{
var albums:Array <Album> = []
for (subJson):(String, JSON) in data["results"]
{
var thumbnail:String = ""
var title:String = ""
var year:String = ""
if let thumbImage = subJson.1["artworkUrl60"].string
{
thumbnail = thumbImage;
}
if let titleString = subJson.1["collectionName"].string
{
title = titleString;
}
if let releaseDate = subJson.1["releaseDate"].string
{
year = releaseDate;
}
let album = Album(_thumbnail: thumbnail, _title: title, _year: year)
albums.append(album)
}
let artist = Artist(_name: name, _musicStyle: "Rock", _albums: albums as NSArray);
self.artistArray.append(artist);
}
}
And I call here in MyClassTableView.m
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
if let artist = searchBar.text
{
self.itunesApi.downloadData(name: artist, { (array) in
print(array);
})
}
}
Why the copmletionHandler return before all method are called? I want to return in first completionHandeler the result of all method but it return before. The self.itunesApi.downloadData return [] instead of an array filled

how to use a property from a class to another class in swift

I have a model class like this
import Foundation
import SwiftyJSON
class SpecificCategoryJSON {
var _product_id: Int?
var _product_title: String?
var _product_sku: String?
var _product_image: URL?
init(items: JSON){
self._product_id = items["product_id"].int
self._product_title = items["product_title"].stringValue
self._product_sku = items["product_sku"].stringValue
let post_imgAA = items["product_image"].arrayValue
for itemsIMG in post_imgAA {
self._product_image = itemsIMG["guid"].URL
}
}
var product_id: Int {
if _product_id == nil {
_product_id = 0
}
return _product_id!
}
var product_title: String {
if _product_title == nil {
_product_title = ""
}
return _product_title!
}
var product_sku: String {
if _product_sku == nil {
_product_sku = ""
}
return _product_sku!
}
var product_image: URL {
if _product_image == nil {
let myURL = "http://www.clker.com/cliparts/d/L/P/X/z/i/no-image-icon-hi.png"
let noImage: URL = URL(string: myURL)!
_product_image = noImage
}
return _product_image!
}
}
I want to use the product_id to send as a parameter to make a POST request using Alamofire . Here is the POST request where I have kept the product_id static .
class ProductDetailsManager {
//........
func printPublicGists() -> Void {
let url: String = "http://xxx/get_product_informations/"
let parameter = ["product_id": 33]
Alamofire.request(url, method: .post, parameters: parameter, encoding: URLEncoding.default, headers: nil)
.responseJSON { (response) in
guard response.result.error == nil else {
print(response.result.error!)
return
}
guard let value = response.result.value else {
print("no product to show")
return
}
print(value)
}
}
//.......
}
I want to get the product_id from the class SpecificCategoryJSON and use it in class ProductDetailsManager to make a call . What should I do , inherit ? override ? UserDefaults.standard.set ? Please help .
Maybe I misunderstood the question, but you should instantiate your SpecificCategoryJSON class and access it's property :
func printPublicGists() -> Void {
let url: String = "http://xxx/get_product_informations/"
let cat = SpecificCategoryJSON(items: someJSON)
let parameter = ["product_id": cat.product_id]
// ....
}

Avoiding duplicating tuples in Swift

In my watchOS2 app I have array of tuples like this:
var medicines = [(String, String?, String?)]()
And in refreshing function i'd like to clear this array of tuples to append it with new String items. How can i do this ? I want to avoid having the same things in my array. Or maybe there is a better idea ?
My refresh function:
let iNeedCoreData = ["Value": "CoreData"]
session.sendMessage(iNeedCoreData, replyHandler: { (content: [String: AnyObject]) -> Void in
if let meds = content["reply"] as? [String: [String]] {
self.medicines = [(String, String?, String?)]()
if let medicineNames = meds["medicines"], amountNames = meds["amount"], timeNames = meds["time"] {
if medicineNames.count != 0 {
self.addMedicines(medicineNames)
self.addQuantities(amountNames)
self.addTime(timeNames)
self.table.setHidden(false)
self.reloadTable()
} else {
self.alertLabel.setHidden(false)
}
}
}
}) { (error) -> Void in
print("We got an error from our watch device:" + error.domain)
}
Adding to tuple funcs:
func reloadTable() {
self.table.setNumberOfRows(medicines.count, withRowType: "tableRowController")
var rowIndex = 0
for item in medicines {
if let row = self.table.rowControllerAtIndex(rowIndex) as? tableRowController {
row.medicineLabel.setText(item.0)
if let quantity = item.1, time = item.2 {
row.amountLabel.setText(quantity)
row.timeLabel.setText(time)
}
rowIndex++
}
}
}
func addMedicines(medicineNames: [String]) {
for name in medicineNames {
medicines.append((name, nil, nil))
}
}
func addQuantities(quantities: [String]) {
guard medicines.count == quantities.count else { return }
for i in 0..<medicines.count {
medicines[i].1 = quantities[i]
}
}
func addTime(timeNames: [String]) {
guard medicines.count == timeNames.count else { return }
for i in 0..<medicines.count {
medicines[i].2 = timeNames[i]
}
}
Once the var has been declared, type hints are no longer needed.
self.medicines = []
I've tried to think of a few ways to overcome your problem here, but your code is very inflexible and needs to be refactored.
You are at the limit for the utility of tuples and need to turn medicine into a class or struct (use a struct) which supports Equatable.
In addition, you need to create an array of new objects, which can be merged into the existing self.medicines, building the new objects directly in self.medicines is very limiting.
Here is the tuple as a struct
struct Medicine: Equatable {
let name: String
let amount: String
let time: String
}
func == (lhs: Medicine, rhs: Medicine) -> Bool {
return lhs.name == rhs.name && lhs.amount == rhs.amount && lhs.time == rhs.time
}
Here is adding new values without removing old values or having duplicates
if let names = meds["medicines"], amounts = meds["amount"], times = meds["time"]
where names.count == amounts.count && names.count == times.count
{
for i in 0..<names.count {
let medicine = Medicine(name: names[i], amount: amounts[i], time: times[i])
if !medicines.contains(medicine) {
medicines.append(medicine)
}
}
}

Resources