Swift 3 unclear delay in filling up labels in a collection view - ios

I am reading products data from API and print down the ids to make sure data has been fetched successfully. Then, I put products titles into collection view label.
The strange thing here is that the list of ids are printed very fast. Then the app wait for few seconds till the collectionView is populated.
I couldn't understand why the delay is occurring as I have only 10 products, which should not take any time for loading and I already made a loop over them and printed ids successfully in no time!
The following code shows exactly what I have done yet. I hope someone can help me figure out where is the bottle-nick:
import UIKit
class TestViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var jsonData : [[String: Any]] = [[:]]
override func viewDidLoad() {
super.viewDidLoad()
var request = URLRequest(url: URL(string: shopUrl + "/admin/products.json")!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with:request, completionHandler: {(data, response, error) in
if error != nil {
print(error)
} else {
do {
guard let json = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] else { return }
guard let root = json?["products"] as? [[String: Any]] else { return }
self.jsonData = root
self.collectionView.reloadData()
print("This will be printed very fast!")
for product in root {
guard let id = product["id"] as? Int else { return }
print(id)
}
}
}
}).resume()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.jsonData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let data = self.jsonData[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TestCollectionViewCell", for: indexPath) as! TestCollectionViewCell
if let title = data["title"] as? String {
cell.titleLabel.text = title
}
return cell
}
}

Try call your reloadData in main thread:
DispatchQueue.main.async {
self.collectionView.reloadData()
}
The problem is that URLSession callback handler is still in background thread so it wont update your UI fast, so you have to switch back to main thread before update any UI after call network request with URLSession

Related

how to get item id when collectionView cell is selected

I just want to pass the item id to the next view controller when the item of collectionView is selected.
here I store the data that I get from API
here's some code -->
var posts = [[String: Any]]()
func apicall() {
let Url = String(format: "http:example.com")
guard let serviceUrl = URL(string: Url) else { return }
var request = URLRequest(url: serviceUrl)
request.httpMethod = "POST"
request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]{
self.posts = (json["data"] as? [[String : Any]])!
DispatchQueue.main.async() {
self.collectionView.reloadData()
}
}
} catch {
print(error)
}
}
}.resume()
}
now I get the data and I want to pass the item id of that item which is selected only
#IBAction func onClickNext(_ sender: Any) {
let controller = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
self.navigationController?.pushViewController(controller, animated: true)
}
here the code of the didSelectItemAt index path
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! secondCollectionViewCell
}
Always get the data from the model, the data source array, never from the view
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = self.posts[indexPath.item]
let id = item["id"]
// do things with id
}
Well at that moment if you have the selection enabled the collection view will be able to return to you the IndexPath for all selected cells in the collection view.
Please take a look to this property on the UICollectionView
var indexPathsForSelectedItems: [IndexPath]? { get }
apple documentation for indexPathForSelectedItems
then at your #IBAction func just do this
#IBAction func onClickNext(_ sender: Any) {
// logic to grab the id from self.posts using the selected indexPaths ie.
let selectedItems = self.collectionView.indexPathsForSelectedItems ?? []
let ids = selectedItems.compactMap { self.posts[$0.row] }
let controller = self.storyboard?.instantiateViewController(withIdentifier:
"secondViewController") as! secondViewController
controller.selectedIds = ids // all the selected ids
self.navigationController?.pushViewController(controller, animated: true)
}
so something like that you should do, i have no idea how the data structure looks like inside your self.posts property but the above code gives you an idea. To simplify this try to run below code in a playground and see the result.
import UIKit
let posts: [String] = ["Carrot_Post", "Pencil_Post", "Dish_Post", "Data_Post",
"iOS_Post", "Kitties_Post", "VideoGamesPost", "Bitcoin_Post"]
let selected: [Int] = [1, 3, 0, 5]
let items: [String] = selected.compactMap({ posts[$0] })
print(items) // output: ["Pencil_Post", "Data_Post", "Carrot_Post", "Kitties_Post"]
Hope that helps with your problem.

Swift get json data and index out of range error

My question is simple but I couldn't find a solution. When I get JSON data from server I want to display the data to collectionviewcell but I got index out of range error.
This is my code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SiparislerTumAnasayfa", for: indexPath) as! SiparislerTumAnasayfa
let url = URL(string: "https://abc/api/SiparislerTumListeler/abc")
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error)
}
else
{
if data != nil{
do{
let responseJSON = try? JSONSerialization.jsonObject(with: data!, options: [])
if let responseJSON = responseJSON as? [String: Any] {
self.jsonArray = responseJSON["results"] as? [[String: Any]]
DispatchQueue.main.async {
let row = self.jsonArray![indexPath.row]
if let urunAdi = row["siparis_urun_adi"] as? String {
cell.siparisUrunAdi.text = urunAdi
}
}
}
}
catch {
print(error)
}
}
}
}
task.resume()
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.jsonArray!.count
}
[indexPath.row] is where I got the error.
You need to add code that handles the case where indexPath.row is greater or equal to self.jsonArray.count.
Maybe the number of array from response api are less than the numberOfItemsInSection
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {....}

why i am unable to download all json values in swift?

I have backend api it contains all values i can see those values in postman.. but while parsing i am unable to download all values from api.. some times i am getting all values.. some times i am not getting only some values.. if i close app and run again then i am getting all values.. again if i close and run or if i go to other viewcontroller and coming back to home then i am missing some values. if i print jsonObj i am not getting all values from api.. why is this happening?
here is my code:
import UIKit
import SDWebImage
struct JsonData {
var iconHome: String?
var typeName: String?
var id: String?
init(icon: String, tpe: String, id: String) {
self.iconHome = icon
self.typeName = tpe
self.id = id
}
}
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UITextFieldDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var itemsArray = [JsonData]()
override func viewDidLoad() {
super.viewDidLoad()
homeServiceCall()
//Do any additional setup after loading the view.
collectionView.delegate = self
collectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemsArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
let aData = itemsArray[indexPath.row]
cell.paymentLabel.text = aData.typeName
cell.paymentImage.sd_setImage(with: URL(string:aData.iconHome ?? ""), placeholderImage: UIImage(named: "varun finance5_icon"))
return cell
}
//MARK:- Service-call
func homeServiceCall(){
let urlStr = "https://dev.com/webservices//getfinancer"
let url = URL(string: urlStr)
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let respData = data else {
return
}
guard error == nil else {
print("error")
return
}
do{
let jsonObj = try JSONSerialization.jsonObject(with: respData, options: .allowFragments) as! [String: Any]
print("the home json is \(jsonObj)")
let financerArray = jsonObj["financer"] as! [[String: Any]]
for financer in financerArray {
guard let id = financer["id"] as? String else { break }
guard let pic = financer["icon"] as? String else { break }
guard let typeName = financer["tpe"] as? String else { break } //changed this one to optional too. Avoid force-unwrapping. Keep everything safe
let jsonDataObj = JsonData(icon: pic, tpe: typeName, id: id)
self.itemsArray.append(jsonDataObj)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
catch {
print("catch error")
}
}).resume()
}
}
Please help me in the above code.
Try network call with background thread.
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
self.homeServiceCall()
}
And the URL is legal with double / as below ?
let urlStr = "https://dev.com/webservices//getfinancer"
And check the all of your backend data's is compatible with your JSONSerialization type of JsonData struct.
I hope it helps.

Reloading UICollection view after data parse

I am trying to dynamically update a uicollectionview. I used this amazing tutorial on how to create a simple uicollection.
It works great when using a static array of items. My issue - I would like to have the uicollection populate with data I parsed into a new array from my db. I am not sure how to reload the uicollection after parsing my json data.
UPDATED CODE WITH ANSWER:
import UIKit
class Books: UIViewController, UICollectionViewDelegate {
#IBOutlet weak var bookscollection: UICollectionView!
var user_id: Int = 0;
//------------ init ---------------//
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
showtutorial()
getuserid()
}
//------------ show books ---------------//
var booknames = [String]()
var bookcolor = [String]()
var bookdescription = [String]()
var bookid = [Int]()
func posttoapi(){
//show loading
LoadingOverlay.shared.showOverlay(view: self.view)
//send
let url:URL = URL(string: "http://www.url.com")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let paramString = "user_id=\(user_id)"
request.httpBody = paramString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest) {(data, response, error) in
//hide loading
LoadingOverlay.shared.hideOverlayView()
//no response
guard let data = data, let _:URLResponse = response, error == nil else {
print("response error")
return
}
//response, parse and send to build
let json = String(data: data, encoding: String.Encoding.utf8);
if let data = json?.data(using: String.Encoding.utf8){
let json = try! JSON(data: data)
for item in json["rows"].arrayValue {
//push data to arrays
self.booknames.append(item["name"].stringValue)
self.bookcolor.append(item["color"].stringValue)
self.bookdescription.append(item["description"].stringValue)
self.bookid.append(item["id"].int!)
//reload uicollection
DispatchQueue.main.sync(execute: {
self. bookscollection.reloadData()
})
}
}
}
task.resume()
}
//------------ collection -------------//
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("collection view code called")
return self.booknames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = bookscollection.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! BooksCell
cell.myLabel.text = self.booknames[indexPath.item]
cell.backgroundColor = UIColor.cyan // make cell more visible in our example project
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
}
//------------ end ---------------//
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Thank you for any help!
Same problem i faced..your url having many images means reload t
dispatch_async(dispatch_get_main_queue(), {
self.collectionView.reloadData()
})

IOS swift how can I populate my TableView with Json data being returned

I have just started working with Swift and am able to do some basic things. Right now I am trying to populate my UITableView with Json Data that I am successfully retrieving. Right now I have this simple Table that looks like this
That is a basic TableView that I was able to create with this code
#IBOutlet var StreamsTableView: UITableView!
let groceries = ["Fish","lobster","Rice","Beans"]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let mycell:UITableViewCell = StreamsTableView.dequeueReusableCell(withIdentifier: "prototype1", for: indexPath)
mycell.textLabel?.text = groceries[indexPath.row]
return mycell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return groceries.count
}
override func viewDidLoad() {
super.viewDidLoad()
StreamsTableView.dataSource = self
}
I now have a JsonRequest that I am completing successfully using this code below
override func viewDidLoad() {
super.viewDidLoad()
StreamsTableView.dataSource = self
// Do any additional setup after loading the view.
var names = [String]()
let urlString = "http://localhost:8000/streams"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let Streams = parsedData["Streams"] as! [AnyObject]? {
for Stream in Streams {
if let post = Stream["post"] as? String {
names.append(post)
}
}
}
} catch let error as NSError {
print(error)
}
print(names)
}
}).resume()
}
What I essentially like to do is put the value of
let post = Stream["post"] as? String
inside the TableView instead of the Groceries array . As I stated before the value is coming back from the Json, I just have not found any way that I could put that value inside the TableView any help would be great . I am using swift 3.0 .
Add reloading data code
DispatchQueue.main.async {
StreamsTableView.reloadData()
}
just after your for loop
for Stream in Streams { ...
if let Streams = parsedData["Streams"] as! [AnyObject]? {
for Stream in Streams {
if let post = Stream["post"] as? String {
names.append(post)
}
}
}
StreamsTableView.reloadData()
After loop done
StreamsTableView.reloadData()
update:
mycell.textLabel?.text = groceries[indexPath.row]
to
mycell.textLabel?.text = names[indexPath.row]

Resources