UICollectionView gives "Index out of range" error - ios

It is a question. Currently I would like to display UITableViewCell in UITableView for the number of users registered in the database. However, it becomes "Index out of range" in the UITableViewDataSOurce method (cellForItemAt).
I understand the meaning of the error, but I do not know why such an error occurs. Could you tell me?
import UIKit
import Firebase
import FirebaseStorage
import FirebaseFirestore
import SDWebImage
struct MemberData {
var image: URL?
var text: String?
}
class MemberSelectViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let statusBsr = UIApplication.shared.statusBarFrame.height
let db = Firestore.firestore()
var teamIDFromFirebase: String = ""
var fireAuthUID = (Auth.auth().currentUser?.uid ?? "no data")
var memberData = [MemberData]()
var memberImageArr = [Any]()
var memberTextArr = [Any]()
var memberUserIDArr = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
let collectionView = UICollectionView(frame: CGRect(x: 0, y: statusBsr, width: self.view.frame.width, height: self.view.frame.size.height - statusBsr), collectionViewLayout: UICollectionViewFlowLayout())
let nibName = UINib(nibName: "memberCollectionViewCell", bundle: nil)
collectionView.register(nibName, forCellWithReuseIdentifier: "memberCell")
collectionView.delegate = self
collectionView.dataSource = self
self.view.addSubview(collectionView)
getMemberData(collectionView: collectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 24
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = self.view.frame.size.width / 4
return CGSize(width: size, height: size)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let inset = (self.view.frame.width / 4) / 5
return UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return (self.view.frame.width / 4) / 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "memberCell", for: indexPath) as! memberCollectionViewCell
let cellData = memberData[indexPath.row]
cell.memberImage.sd_setImage(with: cellData.image)
cell.memberTitle.text = cellData.text
return cell
}
}
private extension MemberSelectViewController {
private func getMemberData(collectionView: UICollectionView) {
self.db.collection("users").document(self.fireAuthUID).addSnapshotListener { (snapshot3, error) in
guard let document3 = snapshot3 else {
print("erorr2 \(String(describing: error))")
return
}
guard let data = document3.data() else { return }
self.teamIDFromFirebase = data["teamID"] as? String ?? ""
self.db.collection("users").whereField("teamID", isEqualTo: self.teamIDFromFirebase).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
return
} else {
var i = 0
for document in querySnapshot!.documents {
guard var documentData: [String: Any] = document.data() else { return }
self.memberImageArr.append((documentData["image"] as? String)!)
self.memberTextArr.append((documentData["name"] as? String)!)
self.memberData.append(MemberData(image: URL(string: self.memberImageArr[i] as! String), text: self.memberTextArr[i] as! String))
i += 1
}
}
}
}
}
}

In this method
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 24
}
You inform the collection view that there will always be 24 rows, so when the view loads, it will start fetching content for 24 rows in the collection view.
However, your memberData array is defined like so:
var memberData = [MemberData]()
Meaning that initially it will be empty.
You then start adding content to memberData in getMemberData, but at that point, your collection view may have already started populating and is asking for content for row number 5 (for instance)...in an array with no elements, and that will crash.
So what you can do is:
Change numberOfItemsInSection to not return a static value, but instead return the actual number of items in your memberData like so:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return memberData.count
}
once you're done loading data into memberData in the getMemberData function, you tell the collection view to reload and it will run through the entire process of building your collection view again, this time with data.
in cellForItemAt you can make sure that you don not try to fetch content that isn't there:
if indexPath.row < memberData.count {
let cellData = memberData[indexPath.row
//and so on
}
Hope that give you some clues.

Related

How to implement the "tags layout" using UICollectionView iOS Swift?

I am trying to implement the Tags Layout using UICollectionView.
It works fine but only at some places the word is not displayed properly (Cropped) (mostly at the end of the UICollectioView frame). I have attached the screenshot as well.
I followed this Setup a collectionView with "tag"-like cells and tried to implement the same but it didn't work for me.
class UserProfileTagsFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributesForElementsInRect = super.layoutAttributesForElements(in: rect) else { return nil }
guard var newAttributesForElementsInRect = NSArray(array: attributesForElementsInRect, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }
var leftMargin: CGFloat = 0.0;
for attributes in attributesForElementsInRect {
if (attributes.frame.origin.x == self.sectionInset.left) {
leftMargin = self.sectionInset.left
}
else {
var newLeftAlignedFrame = attributes.frame
newLeftAlignedFrame.origin.x = leftMargin
attributes.frame = newLeftAlignedFrame
}
leftMargin += attributes.frame.size.width + 8 // Makes the space between cells
newAttributesForElementsInRect.append(attributes)
}
return newAttributesForElementsInRect
}
}
I am using the below array to populate the data :
data = ["Kishor", "iOS", "Developer", "Software Engineer", "ABCD", "Coding", "Xcode", "I am an iOS Developer","JKLMNO" , "Testing Coding", "Development", "XYZ-XYZ-XYZCD", "Enable the Tag Layout", "Layouts Arrangement"]
But if you see in the screenshot, last word of the first line "ABCD", 'D' is cropped and also few of the words are cropped at the end of the frame.
Below is my UICollectionView (Inside the UITableViewCell)
class TagListCell: UITableViewCell {
#IBOutlet weak var tagListCollection: UICollectionView!
var data: [String] = []
override func awakeFromNib() {
super.awakeFromNib()
}
fileprivate func setCollectioView() {
self.setupCollectionnViewLayout()
self.tagListCollection.register(UINib(nibName: "TagListNameCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TagListNameCollectionViewCell")
tagListCollection.delegate = self
tagListCollection.dataSource = self
}
private func setupCollectionnViewLayout() {
let layout = UserProfileTagsFlowLayout()
//layout.estimatedItemSize = CGSize.init(width: 50, height: 50)
layout.scrollDirection = .vertical
tagListCollection.collectionViewLayout = layout
}
// MARK: UIView functions
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
self.tagListCollection.layoutIfNeeded()
let size = tagListCollection.collectionViewLayout.collectionViewContentSize
let newSize = CGSize(width: size.width, height: size.height + 1)
debugPrint("New Size : \(newSize)")
return newSize
}
func setData(data: [String]) {
self.setCollectioView()
self.data = data
}
}
//MARK:- UICollectionView Delegate and DataSource
extension TagListCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TagListNameCollectionViewCell", for: indexPath) as? TagListNameCollectionViewCell {
cell.titleLable.text = data[indexPath.row]
return cell
}
else {
return UICollectionViewCell()
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let tag = data[indexPath.row]
let font = UIFont.systemFont(ofSize: 17)//UIFont(name: "Label Test Data", size: 16)!
let size = tag.size(withAttributes: [NSAttributedString.Key.font: font])
let dynamicCellWidth = size.width
return CGSize(width: dynamicCellWidth, height: 50)
}
// Space between rows
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
// Space between cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
}
I can't figure out how I can adjust the cell sizes without "breaking" rows count.

Collection view auto resizing and setup repeating background

I am using collection view to load data from API. Here i want to extend size of collection view height instead of scrolling inside collection view. And also need to repeat the background image according to collection view height.
Here is the android layout and i want to develop similar to this.Tap here
import UIKit
import Nuke
import SVProgressHUD
import JSONJoy
class HomeViewController: UIViewController {
#IBOutlet weak var categoryCollection: UICollectionView!
#IBOutlet weak var tabbar: UITabBar!
var sectors:[Sector] = []
var timer = Timer()
var counter = 0
var selectedSector = ""
var selectedSectorName = ""
var webService = ApiService()
let plist = PlistHelper()
override func viewDidLoad() {
super.viewDidLoad()
self.categoryCollection.dataSource = self
self.categoryCollection.delegate = self
for item in tabbar.items ?? []{
item.image = item.image?.withRenderingMode(.alwaysOriginal)
}
UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .normal)
UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: .selected)
listSectors()
self.categoryCollection.backgroundColor = UIColor(patternImage: UIImage(named: "bg")!)
}
override func viewWillAppear(_ animated: Bool) {
listbanners()
}
override func viewWillDisappear(_ animated: Bool) {
self.timer.invalidate()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "sectors"){
let vc = segue.destination as! SectorsViewController
vc.sectorCode = selectedSector
vc.sectorName = selectedSectorName
}
}
func listSectors(){
webService.listSectors({ (sectors, message, status) in
if(status){
if let resData = sectors.arrayObject {
do{
for data in resData{
self.sectors.append(try Sector(JSONLoader(data)))
}
DispatchQueue.main.async {
self.categoryCollection.reloadData()
}
}
catch let error {
print("JSonJoyError:\(error)")
}
}
}
})
}
}
extension HomeViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(collectionView == bannerCollection){
return banners.count
}
else {
return sectors.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let options = ImageLoadingOptions(placeholder: UIImage(named: "bannerPlaceholder"),transition: .fadeIn(duration: 0.33))
if(collectionView == bannerCollection){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! DataCollectionViewCell
Nuke.loadImage(with: URL(string: banners[indexPath.row].ImageUrl ?? "")!, options: options, into:cell.img)
return cell
}
else{
let cell = categoryCollection.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath) as! catogeryCollectionViewCell
Nuke.loadImage(with: URL(string: sectors[indexPath.row].ImageUrl ?? "")!, options: options, into:cell.photoImageView)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if(collectionView == categoryCollection){
selectedSector = sectors[indexPath.row].Code ?? "FOOD"
selectedSectorName = sectors[indexPath.row].Name ?? "FOOD"
self.performSegue(withIdentifier: "sectors", sender: self)
}
}
}
extension HomeViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if(collectionView == bannerCollection){
let size = bannerCollection.frame.size
return CGSize(width: size.width, height: size.height - 10)
}
else{
let size = categoryCollection.frame.size
print("size\(size)")
return CGSize(width: (size.width / 2) - 8, height:120)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 30
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
}
Following steps can help you to increase your collection View height according to data.
Create Heightconstraint outlet.
After loading data in collection view with delay of 0.2 sec in main thread,
Set Height Constraint constant = collection view content size height.

CollectionView fatal error: Index out of range

I've been trying to figure out what the issue is in this code for it to throw an index out of range error. However, I am unable to understand where the issue is.
Here is the code
import UIKit
import Alamofire
class MenuViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
let headerId = "headerId"
var itemCategories: [MenuItemCategory]?
var menuItem: [MenuItem]?
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
navigationItem.titleView = UIImageView(image: #imageLiteral(resourceName: "mooyahLabelLogo"))
collectionView?.register(MenuViewControllerCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.register(MenuViewControllerHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
MenuItemCategory.fetchMenuItems { (itemCategories) in
self.itemCategories = itemCategories
self.collectionView?.reloadData()
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
if let count = itemCategories?.count {
print("Number of Sections: \(count)")
return count
}
return 0
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! MenuViewControllerHeader
if let categoryName = itemCategories?[indexPath.section].name {
header.categoryNameLabel.text = categoryName
}
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 44)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = itemCategories?[section].items?.count {
print("Number of Items in Section: \(count)")
return count
}
return 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuViewControllerCell
if let category = itemCategories?[indexPath.item] {
print("Section: \(category.name!)")
if let itemsCount = category.items?.count {
for i in 0..<itemsCount {
print("Item: \(category.items?[i].name ?? "")")
cell.itemNameLabel.text = category.items?[i].name ?? ""
cell.itemDescriptionLabel.text = category.items?[i].desc ?? ""
if let price = category.items?[i].price {
cell.itemPriceLabel.text = "AED \(price)"
}
}
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 85)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Here is the debugger print where it shows that my number of sections are correct as well as the number of items in section is correct. I am not sure where the issue arises from?
Debugger screenshot
In override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
Shouldn't this line be
if let category = itemCategories?[indexPath.section] { .... }
Not
if let category = itemCategories?[indexPath.item] { .... }
I would suggest to use a guard to make sure that your items are available like this
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuViewControllerCell
guard let itemCategories = itemCategories, itemCategories.indices.contains(indexPath.item), let items = category.items else {
return cell
}
if let category = itemCategories[indexPath.item] {
print("Section: \(category.name!)")
if let itemsCount = items.count {
for i in 0..<itemsCount {
if items.indices.contains(i) {
print("Item: \(items[i].name ?? "")")
cell.itemNameLabel.text = items[i].name ?? ""
cell.itemDescriptionLabel.text = items[i].desc ?? ""
if let price = category.items?[i].price {
cell.itemPriceLabel.text = "AED \(price)"
}
}
}
}
}
return cell
}

UICollectionView duplicates cells when returning from another view controller

I have been working on a weather app and been using UICollectionView to display weather data.
Whenever I open another view controller and return back to the UICollectionView's View controller, I get duplicate cells.
Here is the code.
I use Alamofire to make api requests, append the json result to a local string and then assign it to the cell's text labels.
class VaanizhaiViewController: UICollectionViewController {
// MARK: - flow layout
let columns : CGFloat = 2.0
let inset : CGFloat = 3.0
let spacing : CGFloat = 3.0
let lineSpacing : CGFloat = 3.0
var isRandom : Bool = false
// MARK: - Variables
var apiKey: String = "55742b737e883a939913f2c90ee11ec0"
var country : String = ""
var zipCode : String = ""
var json : JSON = JSON.null
var cityNames: [String] = []
var temperature: [Int] = []
var weather: [String] = []
// MARK: - Actions
// MARK: - view did load
override func viewDidLoad() {
super.viewDidLoad()
let layout = BouncyLayout()
self.collectionView?.setCollectionViewLayout(layout, animated: true)
self.collectionView?.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
for i in 0...zip.count-1{
parseURL(zipCode: "\(zip[i])", country: "us")
}
}
// MARK: - Functions
func parseURL(zipCode: String, country: String){
let url = "http://api.openweathermap.org/data/2.5/weather?zip=\(zipCode),\(country)&APPID=\(apiKey)&units=metric"
requestWeatherData(link: url)
}
func requestWeatherData(link: String){
Alamofire.request(link).responseJSON{ response in
if let value = response.result.value{
self.json = JSON(value)
self.cityNames.append(self.json["name"].string!)
let cTemp = ((self.json["main"]["temp"].double)!)
self.temperature.append(Int(cTemp))
let cWeather = self.json["weather"][0]["main"].string!
self.weather.append(cWeather)
self.collectionView?.reloadData()
}
}
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.cityNames.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "weatherCell", for: indexPath) as! WeatherViewCell
if !self.cityNames.isEmpty {
cell.cityLabel.text = self.cityNames[indexPath.row]
cell.tempLabel.text = String (self.temperature[indexPath.row])
cell.weatherLabel.text = self.weather[indexPath.row]
cell.backgroundColor = UIColor.brown
}
return cell
}
// MARK: - UICollectionViewDelegateFlowLayout
extension VaanizhaiViewController: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = Int ((CGFloat(collectionView.frame.width) / columns) - (inset + spacing))
return CGSize(width: width, height: width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: CGFloat(inset), left: CGFloat(inset), bottom: CGFloat(inset), right: CGFloat(inset))
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return lineSpacing
}
}
Your viewDidAppear will be called every time you visit the view controller, and your viewDidLoad will only get called once, when it's created.
Therefore, you should probably switch your call to self.collectionView?.reloadData() to the viewDidAppear and your call to parseURL to the viewDidLoad
if you need to update your weather data (on a refresh, for example), then you need to restructure your requestWeatherData to stop appending to your arrays, and replace instead.

UICollectionView reloadData() does not update cells in the collection view

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

Resources