1

Here's high level description of what I'm trying to achieve; 1. fetch data 2. save the fetched data in an array object 3. update collection view with the size of the array

Here's my code

class ItemcollectionViewController:UICollectionViewController, UICollectionViewDelegateFlowLayout {

  let cellId = "CellId"
  var categories = [Category]()
  let viewOptionVar:ViewOptionBar = {
      let vOV = ViewOptionBar()
      vOV.translatesAutoresizingMaskIntoConstraints = false
      return vOV
  }()

  private func fetchData() {
      let categoryController = CategoryController()
      categoryController.getAllCategory(username: "blah", password: "password") {(returnedCategories, error) -> Void in
            if error != nil {
               print(error)
               return
            }
            self.categories = returnedCategories!
            print("size of the array is \(self.categories.count)")
            OperationQueue.main.addOperation{self.collectionView?.reloadData()}
      }

  }

  override func viewDidLoad() {
    super.viewDidLoad()
    fetchData()
    collectionView?.backgroundColor = UIColor.white
    collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
    collectionView?.contentInset = UIEdgeInsetsMake(50, 0, self.view.frame.height, self.view.frame.width)
    collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, self.view.frame.width)
  }

  override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    print("in the method \(self.categories.count)")
    return self.categories.count
  }

  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ItemCell
    cell.category = categories[indexPath.item]
    return cell
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: 111, height: 111)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 0
  }

  private func setupViweOptionBar() {
    view.addSubview(viewOptionVar)
    view.addConstraintsWithFormat(format: "H:|[v0]|", views: viewOptionVar)
    view.addConstraintsWithFormat(format: "V:|[v0(50)]", views: viewOptionVar)
  }
}

In the log I could see the following statements:

in the method 0

size of the array is 3

and could not see any cell from my view.

Can someone advise me what I've done wrong? Thanks in advance.

EDIT 1 Now I'm fetching data after registering the customised cells. However, it still doesn't work

updated code:

class ItemcollectionViewController:UICollectionViewController, UICollectionViewDelegateFlowLayout {

  let cellId = "CellId"
  var categories = [Category]()
  let viewOptionVar:ViewOptionBar = {
      let vOV = ViewOptionBar()
      vOV.translatesAutoresizingMaskIntoConstraints = false
      return vOV
  }()

  private func fetchData() {
      let categoryController = CategoryController()
      categoryController.getAllCategory(username: "blah", password: "password") {(returnedCategories, error) -> Void in
            if error != nil {
               print(error)
               return
            }
            self.categories = returnedCategories!
            print("size of the array is \(self.categories.count)")

      }

  }

  override func viewDidLoad() {
    super.viewDidLoad()
    collectionView?.backgroundColor = UIColor.white
    collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
    collectionView?.contentInset = UIEdgeInsetsMake(50, 0, self.view.frame.height, self.view.frame.width)
    collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, self.view.frame.width)
    collectionView?.dataSource = self
    collectionView?.delegate = self
    fetchData()
    DispatchQueue.main.async{self.collectionView?.reloadData()}
  }

  override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    print("in the method \(self.categories.count)")
    return self.categories.count
  }

  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ItemCell
    cell.category = categories[indexPath.item]
    return cell
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: 111, height: 111)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 0
  }

  private func setupViweOptionBar() {
    view.addSubview(viewOptionVar)
    view.addConstraintsWithFormat(format: "H:|[v0]|", views: viewOptionVar)
    view.addConstraintsWithFormat(format: "V:|[v0(50)]", views: viewOptionVar)
  }
}

EDIT 2

The following code is my querying method

func getAllCategory(username:String, password:String, callback: @escaping ([Category]?, String?) -> Void){
    var categories = [Category]()
    let fetchCategories = URL(string: userURL + "all")
    URLSession.shared.dataTask(with: fetchCategories!, completionHandler: { (data, response, error) in
        if let err = error {
            print(err)
            return
        }
        do {
            let jsonCategoryObj = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [[String: AnyObject]]
            for categoryDictionary in jsonCategoryObj {
                let category = Category()
                category.categoryId = categoryDictionary["categoryId"] as? String
                category.categoryName = categoryDictionary["categoryName"] as? String
                category.categoryDescription = categoryDictionary["categoryDescription"] as? String
                let categoryRegisteredDateString = categoryDictionary["categoryRegisteredDate"] as? String
                let df = DateFormatter()
                df.dateFormat = self.shapeDateFormat
                let categoryRegisteredDate = df.date(from: categoryRegisteredDateString!)!
                category.categoryRegisteredDate = categoryRegisteredDate

                categories.append(category)

            }
            callback(categories, nil)
        }catch let jsonError {
            callback(nil, String(describing: jsonError))
        }

    }).resume()
}

FYI: I know I'm not using passed user credential, it's just a copy and paste error from my different query method

J.GS.Han
  • 117
  • 1
  • 9
  • Hello. Did you get a solution to this problem, or you just gave uo the way i'm about to? – nyxee Jul 21 '17 at 14:36

3 Answers3

10

When the DataSource changes, reloadData does not update the cell that has been displayed in the view. Reload visible items will do this job.

    self.collectionView.reloadData()
    self.collectionView.performBatchUpdates({ [weak self] in
        let visibleItems = self?.collectionView.indexPathsForVisibleItems ?? []
        self?.collectionView.reloadItems(at: visibleItems)
    }, completion: { (_) in
    })
Jimi
  • 1,091
  • 1
  • 14
  • 19
4

I'm not sure how this resolved this issue. But I just added

print("size of the array is \(self.categories?.count)")

just next to

OperationQueue.main.addOperation{self.collectionView?.reloadData()}

and it magically works.. even thought when I go back and come back to the screen, it does not show anything.

I'll investigate it more and try to find out why this is happening

Updated

Using

DispatchQueue.main.sync

instead of

DispatchQueue.main.async

resolved the problem.

J.GS.Han
  • 117
  • 1
  • 9
  • hello. i don;t understand what you mean by putting that line next to the other. do you mean you put the line before the Queue call? – nyxee Jul 21 '17 at 14:47
1
DispatchQueue.main.async{self.collectionView?.reloadData()}
    collectionView?.backgroundColor = UIColor.white
    collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)

You are reloading data before you even have registered your cell.

Register your cell and datasource and delegate methods FIRST, and reload data LAST.

EDIT:

I see you edited your post.

But again, you are fetchData() before you even have registered your cell. So, again, move the fetchData method AFTER you have registered all cells , datasources and delegates.

  • That makes sense. However, it didn't work even after I changed it to `collectionView?.backgroundColor = UIColor.white collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId) collectionView?.contentInset = UIEdgeInsetsMake(50, 0, self.view.frame.height, self.view.frame.width) collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, self.view.frame.width) fetchData() DispatchQueue.main.async{self.collectionView?.reloadData()}` – J.GS.Han Feb 15 '17 at 06:10
  • @J.GS.Han You are not setting the collectionView.delegate = self and collectionView.datasource = self , shouldve made it more clear in my answer but that is your only problem now. Also I think you need **UICollectionViewDataSource** and **UICollectionViewDelegate** added to your code in your class. –  Feb 15 '17 at 06:13
  • Thanks for your reply! I've tried to update my code based on your suggested solution but it didn't work as well. I also tried to add **UICollectionViewDataSource** and **UICollectionViewDelegate** to my class but it's complaining with saying "Redundant conformance of 'ItemcollectionViewController' to protocol 'UICollectionViewDataSource''" – J.GS.Han Feb 15 '17 at 06:27
  • @J.GS.Han I see you are using the UICollectionViewController, then setting self is all you need , and you should be good to go. As long as you make sure you call reloadData when you change the number of items in numberOfItemsInSection method. I can't test your code however since I don't code in Swift, but generally I dont see any errors other than these I pointed out for you. –  Feb 15 '17 at 06:32
  • what I've tried is adding **UICollectionViewDataSource** and **UICollectionViewDelegate** as my class' super classes – J.GS.Han Feb 15 '17 at 06:35
  • what do you mean by "you change the number of items in numberOfItemsInSection method"? Do i need to call reloadData method inside that method? rather than calling it in viewDidLoad method? – J.GS.Han Feb 15 '17 at 06:37
  • @J.GS.Han No you should only call reloadData when you change your datasource after you fetchData, but I see you do that already. NVM, I saw that Swift 3 has changed how you register the cell. I don't see any more errors :( –  Feb 15 '17 at 06:42
  • Can you point me out where's wrong? it seems correct to me according to the thread you've linked... I've added my query method to my question, can you have a look if there is any problem? – J.GS.Han Feb 15 '17 at 06:43
  • @J.GS.Han Do you see any number of Cells at all on the screen? Does your cellForItemAt:indexPath: return anything at all? Also you are 100% sure that your print in numberOfItems is positive number ? –  Feb 15 '17 at 06:44
  • @J.GS.Han I think the problem now finally is in your self.categories not returning a positive number of items. Make sure you see the number of items, also you can test this manually by just comment out self.categoris and set a manual count to eexample 5 items in numberOfItems method. –  Feb 15 '17 at 06:50
  • That shouldn't be the case. As I added couple of debugging statement there and they were logging the size of item correctly; correct number of item inside the fetch function, 0 item in numberOfItemAtIndex method – J.GS.Han Feb 15 '17 at 10:17
  • @J.GS.Han You are saying you have 0 items in numberOfItemAtIndex ? Then of course no cells are showing up, since you are telling your datasource that you have no items to show. Since you have 0 items , the cellForRow will never get called and not return any cells. –  Feb 15 '17 at 10:22
  • So currently this is happening: when it registers a cell class, it invokes 'numberOfItemAtIndex' method once - this time of course array is empty. Then when it gets into the fetch method and prints the correct number of items. My thought was.. as the next step, as we reload data, it should call 'numberOfItemAtIndex' again and print the correct number of items. But what actually happened was it didn't get into 'numberOfItemAtIndex' again and print nothing – J.GS.Han Feb 15 '17 at 10:35
  • @J.GS.Han You have edited your question so much I lost track, You need to reloadData when you have fetched the categories and set self.categoris in your fetchData function. reloadData After this line **self.categories = returnedCategories!** –  Feb 15 '17 at 10:38
  • that's what's happening right now. I've tried both of 'collectionView.reloadData()' and 'DispatchQueue.main.async{self.collectionView?.reloadData()}'. But still logging same thing – J.GS.Han Feb 15 '17 at 10:44
  • @J.GS.Han If your self.categories has a count > 0 , and your numberOfItems is > 0 , then you should get cells. However, I noticed you don't have sections set. https://developer.apple.com/reference/uikit/uicollectionviewdatasource/1618023-numberofsections , add this delegate and return 1 section, see if that helps your final problems. –  Feb 15 '17 at 10:52
  • @Sneak, i have a similar problem and my sections are st, and when i pass artificial data, everything works perfect. when i pass data from a server (in a background thread and then using a DispatchQueue for self.collectionView?.reload), i get a correct numberCount, i can see my data is there in my datasource objects, but i have only the old indexPaths. – nyxee Jul 21 '17 at 14:45
  • @nyxee It is very hard to know exactly what is wrong, make sure you read through the comment I made above and check everything is correct. In short , I suspect (logically) the problem should be that your **reloadData** is called before your background thread is finished loading the items into your dataSource . I noticed you wrote **self.collectionView?.reload)** , I don't code in swift, but shouldnt it be **reloadData** and not reload? If it doesnt help, i suggest you make a new question and post your code it will be easier to find the problem. –  Jul 21 '17 at 16:19