how to get item id when collectionView cell is selected - ios

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.

Related

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.

data getting fetched from server after assigning it to collection view

I am new to swift language so not sure how to resolve this issue. Here I am trying to display images using uicollectionview. But I not getting the proper output as it does not show anything on collection view when executed. Need help friends.
View Did Load Function
override func viewDidLoad() {
super.viewDidLoad()
ImageGet()
}
Collection View
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(defectImages.count) // returns zero value here
return defectImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
cell.image.image = defectImages[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let largeVC = mainStoryBoard.instantiateViewController(withIdentifier: "ImageDisplayVC") as! ImageDisplayVC
largeVC.imgImage = defectImages[indexPath.row]
self.navigationController?.pushViewController(largeVC, animated: true)
}
Alamofire to get images
func ImageGet() {
let imageId = Int(details.id!)
let para: Parameters = ["imageId": imageId]
Alamofire.request(URL_IMG_List, method: .post, parameters: para).responseJSON { response in
if((response.result.value) != nil) {
let swiftyJsonVar = JSON(response.result.value!)
if let resData = swiftyJsonVar["data"].arrayObject {
self.arrRes = resData as! [[String:AnyObject]]
for index in 0..<self.arrRes.count{
self.imageData.file_name = self.arrRes[index]["file_name"] as! String
self.completeImagePath = self.get_image_path + self.imageData.file_name
self.imgpath.append(self.completeImagePath)
guard let url = URL(string: self.completeImagePath) else {return}
print(url)
if let data = try? Data(contentsOf: url) {
guard let image: UIImage = UIImage(data: data) else {return}
print(image)
self.defectImages.append(image as UIImage)
}
}
print(self.defectImages.count)
}
}
}
}
You just need to reload your collectionView once you fetch data from API and please check that you set your collectionView dataSource and delegate from storyBoard. if not than write below lines in viewDidLoad() before ImageGet().
self.collectionView.dataSource = self
self.collectionView.delegate = self
Replace below code with yours.
func ImageGet() {
let imageId = Int(details.id!)
let para: Parameters = ["imageId": imageId]
Alamofire.request(URL_IMG_List, method: .post, parameters: para).responseJSON { response in
if((response.result.value) != nil) {
let swiftyJsonVar = JSON(response.result.value!)
if let resData = swiftyJsonVar["data"].arrayObject {
self.arrRes = resData as! [[String:AnyObject]]
for index in 0..<self.arrRes.count{
self.imageData.file_name = self.arrRes[index]["file_name"] as! String
self.completeImagePath = self.get_image_path + self.imageData.file_name
self.imgpath.append(self.completeImagePath)
guard let url = URL(string: self.completeImagePath) else {return}
print(url)
if let data = try? Data(contentsOf: url) {
guard let image: UIImage = UIImage(data: data) else {return}
print(image)
self.defectImages.append(image as UIImage)
}
self.collectionView.reloadData() // RELOAD COLLECTIONVIEW
}
print(self.defectImages.count)
}
}
}
}

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()
})

How do I fix laggy UITableView scrolling performance when downloading JSON?

In my application, I download a JSON file off of the internet and fill up a UITableView with items from the file. It does work well, and there are no problems or errors, but the scrolling performance is very laggy, and the UI glitches out a tiny bit.
I assume this is because of the images that I'm downloading from the JSON file, so I've looked into multi-threading, but I don't think I am doing it right because it does load much faster, but scrolling performance is still the same as before.
Can somebody please tell me how to fix this? This UITableView is the most important thing in the app, and I have been spending much time on trying to fix it. Thank you!
Here is my code-
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var nameArray = [String]()
var idArray = [String]()
var ageArray = [String]()
var genderArray = [String]()
var descriptionArray = [String]()
var imgURLArray = [String]()
let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
final let urlString = "https://pbsocfilestorage.000webhostapp.com/jsonDogs.json"
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithURL()
// Activity Indicator
myActivityIndicator.center = view.center
myActivityIndicator.hidesWhenStopped = true
myActivityIndicator.startAnimating()
view.addSubview(myActivityIndicator)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func downloadJsonWithURL() {
let url = NSURL(string:urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) ->
Void in
print("Good so far...")
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
print(jsonObj!.value(forKey: "dogs"))
if let dogArray = jsonObj!.value(forKey: "dogs") as? NSArray {
print("Why u no work!")
for dog in dogArray {
if let dogDict = dog as? NSDictionary {
if let name = dogDict.value(forKey: "name") {
self.nameArray.append(name as! String)
}
if let name = dogDict.value(forKey: "id") {
self.idArray.append(name as! String)
}
if let name = dogDict.value(forKey: "age") {
self.ageArray.append(name as! String)
}
if let name = dogDict.value(forKey: "gender") {
self.genderArray.append(name as! String)
}
if let name = dogDict.value(forKey: "image") {
self.imgURLArray.append(name as! String)
}
if let name = dogDict.value(forKey: "description") {
self.descriptionArray.append(name as! String)
}
OperationQueue.main.addOperation ({
self.myActivityIndicator.stopAnimating()
self.tableView.reloadData()
})
}
}
}
}
}).resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let imgURL = NSURL(string: imgURLArray[indexPath.row])
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell") as! TableViewCell
URLSession.shared.dataTask(with: (imgURL as! URL), completionHandler: {(data, resp, error) -> Void in
if (error == nil && data != nil) {
OperationQueue.main.addOperation({
cell.dogNameLabel.text = self.nameArray[indexPath.row]
cell.idLabel.text = self.idArray[indexPath.row]
cell.ageLabel.text = self.ageArray[indexPath.row]
cell.genderLabel.text = self.genderArray[indexPath.row]
print("Cell info was filled in!")
if imgURL != nil {
let data = NSData(contentsOf: (imgURL as? URL)!)
cell.dogImage.image = UIImage(data: data as! Data)
}
})
}
}).resume()
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDog" {
if let indexPath = self.tableView.indexPathForSelectedRow{
let detailViewController = segue.destination as! DetailViewController
detailViewController.imageString = imgURLArray[indexPath.row]
detailViewController.nameString = nameArray[indexPath.row]
detailViewController.idString = idArray[indexPath.row]
detailViewController.ageString = ageArray[indexPath.row]
detailViewController.descriptionString = descriptionArray[indexPath.row]
detailViewController.genderString = genderArray[indexPath.row]
}
}
}
}
There is a big mistake. You are loading data with dataTask but you aren't using that returned data at all. Rather than you are loading the data a second time with synchronous contentsOf. Don't do that.
And don't update the labels in the asynchronous completion block. The strings are not related to the image data.
This is more efficient:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let imgURL = URL(string: imgURLArray[indexPath.row])
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! TableViewCell
cell.dogNameLabel.text = self.nameArray[indexPath.row]
cell.idLabel.text = self.idArray[indexPath.row]
cell.ageLabel.text = self.ageArray[indexPath.row]
cell.genderLabel.text = self.genderArray[indexPath.row]
print("Cell info was filled in!")
URLSession.shared.dataTask(with: imgURL!) { (data, resp, error) in
if let data = data {
OperationQueue.main.addOperation({
cell.dogImage.image = UIImage(data: data)
})
}
}.resume()
return cell
}
Note: You are strongly discouraged from using multiple arrays as data source. It's very error-prone. Use a custom struct or class. And create imgURLArray with URL instances rather than strings. This is also much more efficient.
Nevertheless, you should use a download manager which caches the images and cancels downloads if a cell goes off-screen. At the moment each image is downloaded again when the user scrolls and cellForRow is called again for this particular cell.

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

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

Resources