How to loop through array and display in NSCollectionViewItem

I am pulling in JSON data from a url and displaying my data in labels in a .xib. The .xib is being called as a NSCollectionViewItem via the identifier "test". Instead of each of my items being displayed it is only showing the last element of the array. Any help would be greatly appreciated.


ViewController.swift

import Cocoa
class ViewController: NSViewController {
  
    var products: [Product]?
  
    override func viewDidLoad() {
      
        super.viewDidLoad()
        }
    override var representedObject: Any? {
        didSet {
        /
        }
    }
}
extension ViewController: NSCollectionViewDataSource {
  
    func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
      
        if let count = products?.count {
            return count
        }
        return 3
    }
    func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
      
        let item = collectionView.makeItem(withIdentifier: "test", for: indexPath)
      
        return item
    }
}


test.swift

import Cocoa
class test: NSCollectionViewItem {
    @IBOutlet weak var label: NSTextField!
    @IBOutlet weak var label2: NSTextField!
   
    var names: [String] = []
    var prices: [String] = []
   
   
    override func viewDidLoad() {
        super.viewDidLoad()
        /
       
       
       
       
        let requestURL: NSURL = NSURL(string: "http:/
        let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL as URL)
        let session = URLSession.shared
        let task = session.dataTask(with: urlRequest as URLRequest) {
            (data, response, error) -> Void in
           
            let httpResponse = response as! HTTPURLResponse
            let statusCode = httpResponse.statusCode
           
            if (statusCode == 200) {
               
                do{
                   
                    let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments)
                   
                    if let products = json as? [[String: Any]] {
                       
                        for product in products {
                           
                           
                            if let name = product["product_name"] as? String {
                               
                               
                                if let price = product["product_price"] as? String {
   
                                   
                                    self.names.append(name as String)
                                    self.prices.append(price as String)
                                   
                                   
                                        DispatchQueue.main.async() {
                                           
                                            for (index, _) in self.names.enumerated() {
                                                self.label.stringValue = self.names[index]
                                            }
                                            for (index, _) in self.prices.enumerated() {
                                                self.label2.stringValue = self.prices[index]
                                            }
                                           
                                        }
                                   
                                   
                                    print(name,price)
                                   
                                   
                                }
                            }
                        }
                       
                    }
                   
                }catch {
                   
                    print("Error with Json: \(error)")
                }
               
            }
        }
       
        task.resume()
       
       
       
    }
   
}
class Product: NSObject {
   
    var id: NSNumber?
    var product_name: String?
    var product_price: String?
   
}

Accepted Reply

Well, it's doing what you're telling it to do. 😉


First of all it is incorrect (apparently, based on the code you've shown) to retrieve the JSON data from an instance of your NSCollectionViewItem subclass. There is going to be one instance per item. You are retrieving the JSON data over and over again in each item, each time throwing away the previous data.


Second, instead of putting the decoded JSON data in your view controller's "products" array, you're saving the individual values (name, price) inside arrays in the NSCollectionViewItem instances. Thus, your "products" array is never going to have the correct count, nor the correct product details (unless something happens in code you don't show us).


Third, by iterating "for product in products" per item, you set the value of the item view labels at each iteration, which of course leaves only the last set of label values to be displayed.


You should start familiarizing yourself with the MVC (model-view-controller) design pattern, and that should help you start to structure your code properly:


developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

Replies

Well, it's doing what you're telling it to do. 😉


First of all it is incorrect (apparently, based on the code you've shown) to retrieve the JSON data from an instance of your NSCollectionViewItem subclass. There is going to be one instance per item. You are retrieving the JSON data over and over again in each item, each time throwing away the previous data.


Second, instead of putting the decoded JSON data in your view controller's "products" array, you're saving the individual values (name, price) inside arrays in the NSCollectionViewItem instances. Thus, your "products" array is never going to have the correct count, nor the correct product details (unless something happens in code you don't show us).


Third, by iterating "for product in products" per item, you set the value of the item view labels at each iteration, which of course leaves only the last set of label values to be displayed.


You should start familiarizing yourself with the MVC (model-view-controller) design pattern, and that should help you start to structure your code properly:


developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

Thank you very much for your response, this was very helpful. I see how my JSON is being retrieved once per item. I guess I still need to learn to proper way to access my JSON data. My inital thoughts of putting my JSON data into the NSCollectionViewItem subclass was the fact that my labels representing my product_name and product_price can only be linked via an outlet in my test.swift file. I am not sure how to access it from another class.


The second issue i had a feeling I wasnt doing the product count correctly.


The third issue I can see how I am leaving the last set of values everytime. My issue is that I am developing this app on MAC OS X not iOS and the syntax for mobile vs desktop is drastically different and I know that the same basic idea and MVC model should aply but I am having a hard time finding some docuemntation or tutorials showing how to parse JSON currectly in a NSCollectionView for desktop applications. If you have any other documentation or help on this subject that would be greatly appreciated.

I worked on some of my code to what I think is the correct way to do it but now I have no errors, no warnings, and a completely blank window when I run my project. Neither of my lables show up and it is completely blank. my test.xib file has two labels that are not being populated. My arrays are still being printed out in the console correctly. I noticed that in iOS they use item.label.stringValue = self.names[indexPath.row]. In this case "row" was not a member to use. I have a feeling something is wrong with my code using .item and casting it as a String. Any help of suggestions would be greatly appreciated. Here is my updated code:


test.swift

import Cocoa
class test: NSCollectionViewItem {
    @IBOutlet weak var label: NSTextField!
    @IBOutlet weak var label2: NSTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    
    }
}


ProductModel.swift

import Cocoa
class ProductModel: NSObject {

        var id: NSNumber?
        var product_name: String?
        var product_price: String?
     
}


ViewController.swift

import Cocoa
class ViewController: NSViewController, NSCollectionViewDelegate {
  
    var products: [ProductModel]?
  
    var names: [String] = []
    var prices: [String] = []
  
    override func viewDidLoad() {
      
        super.viewDidLoad()
      
        getJSON()
      
    }
  
    func getJSON() {
      
        let requestURL: NSURL = NSURL(string: "http:/
        let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL as URL)
        let session = URLSession.shared
        let task = session.dataTask(with: urlRequest as URLRequest) {
            (data, response, error) -> Void in
          
            let httpResponse = response as! HTTPURLResponse
            let statusCode = httpResponse.statusCode
          
            if (statusCode == 200) {
              
                do{
                  
                    let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments)
                  
                    if let products = json as? [[String: Any]] {
                      
                        for product in products {
                          
                          
                            if let name = product["product_name"] as? String {
                              
                              
                                if let price = product["product_price"] as? String {
                                  
                                  
                                    self.names.append(name as String)
                                  
                                    self.prices.append(price as String)
                                  
                                }
                            }
                        }
                      
                    }
                  
                    print("Name Array: \(self.names)", "Price Array: \(self.prices)")
                  
                  
                  
                }catch {
                  
                    print("Error with Json: \(error)")
                }
              
            }
        }
      
        task.resume()
    }
    override var representedObject: Any? {
        didSet {
        /
        }
    }
}
extension ViewController: NSCollectionViewDataSource {
  
    func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
      
        return names.count
    }
    func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
      
        let item: test = collectionView.makeItem(withIdentifier: "test", for: indexPath) as! test
            item.label.stringValue = self.names[indexPath.item] as String
            item.label2.stringValue = self.prices[indexPath.item] as String
        return item
    }
}

The likely cause of the empty collection display is that you've done nothing to inform the collection view, when the JSON retrieval completes, that the number of items has changed. As far as the collection view is concerned, the number of items is still the same as when it first asked via collectionView(_:numberOfItemsInSection:), that is, zero. Because loading the JSON data is asynchronous, the actual number of items isn't know until much later.


After you've loaded the data you should inform the collection view by calling reloadData().

Are you saying I need to add some sort of completion handler to my json code? And then afterwards reload()?

No, I'm saying that immediately after the "for product in products" loop you've finished updating the arrays. At that point you can call reloadData().


Note, however, that it may be necessary to call reloadData() from the main thread, if it causes a UI update directly. (The documentation isn't specific on this point.) If that's so, and if your URLSession delegate queue is something other than the main queue, you will need to dispatch the reloadData() call back to the main queue.

Thank you so much for the help. I really appreciate you taking the time to explain the process to me. I have defintiely learned a lot!

In case this helps anyone I am ging to post my code again with the correct MVC format. This code is successfully displaying in a collectionView using a .xib file as my NSCollectionViewItem. I hope this helps someone who needs any help with displaying JSON data in a NSCollectionView for MAC Desktop Applications. Thank you to QuinceyMorris for your guidance.


ProductModel.swift

import Cocoa
class ProductCategory: NSObject{

    var name: String?

    var product: [ProductModel]?
}
class ProductModel: NSObject {

        var id: NSNumber?
        var product_name: String?
        var product_price: String?
        var product_description: String?
        var product_image: String?
        var product_video: String?
        var product_download: String?
}


test1.swift

import Cocoa
class test1: NSCollectionViewItem {
    @IBOutlet weak var label: NSTextField!
    @IBOutlet weak var label2: NSTextField!
    @IBOutlet weak var label3: NSTextField!
  
    var productItem: ProductModel?
  
  
    var buildProduct: ProductModel? {
      
        didSet{
            label.stringValue = (buildProduct?.product_name)!
            label2.stringValue = (buildProduct?.product_price)!
            label3.stringValue = (buildProduct?.product_image)!
          
          
        }
    }
  
    override func viewDidLoad() {
        super.viewDidLoad()
      
    }
}


ViewController.swift

import Cocoa
class ViewController: NSViewController, NSCollectionViewDelegate {
   
    @IBOutlet weak var colView: NSCollectionView!
   
    var productCategories: [ProductCategory]?
   
    var productsArray: [ProductModel]?
   
    var names: [String] = []
    var prices: [String] = []
    var images: [String] = []
   
    override func viewDidLoad() {
       
        super.viewDidLoad()
       
   
        getJSON()
       
        colView.dataSource = self
        colView.delegate = self
       
        let nib = NSNib(nibNamed: "test1", bundle: nil)
        colView.register(nib, forItemWithIdentifier: "test1")
       
       
    }
   
    func getJSON() {
       
        let requestURL: NSURL = NSURL(string: "http:/
        let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL as URL)
        let session = URLSession.shared
        let task = session.dataTask(with: urlRequest as URLRequest) {
            (data, response, error) -> Void in
           
            let httpResponse = response as! HTTPURLResponse
            let statusCode = httpResponse.statusCode
           
           
           
            if (statusCode == 200) {
               
                do{
                   
                    let json = try JSONSerialization.jsonObject(with: data!, options:.mutableContainers)
                   
                    self.productsArray = [ProductModel]()
                   
                    if let products = json as? [[String: Any]] {
                       
                        for product in products {
                           
                            let testproduct = ProductModel()
                            testproduct.product_name = product["product_name"] as? String
                            testproduct.product_price = product["product_price"] as? String
                            testproduct.product_image = product["product_image"] as? String
                            self.productsArray?.append(testproduct)
                           
                        }
                       
                        DispatchQueue.main.async(){
                        self.colView.reloadData()
                        }
                    }
                   
                }catch {
                   
                    print("Error with Json: \(error)")
                }
               
            }
        }
       
        task.resume()
    }
    override var representedObject: Any? {
        didSet {
        /
        }
    }
}
extension ViewController: NSCollectionViewDataSource {
   
    func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
       
        return productsArray?.count ?? 0
    }
    func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
       
        let item = collectionView.makeItem(withIdentifier: "test1", for: indexPath) as! test1
       
            item.buildProduct  = productsArray?[indexPath.item]
       
        return item
    }
}