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.
Related
I am new to swift,
I am working on an application
I Have 2 different API to get banner data and tableView Data, for the Images I am getting URL from server .
After the Banner is loaded and running I am populating the UITableView,
problem is either the UITableView visible after sometime (around 1 min) or I have to tap on the screen to make it visible
Also the banner is not scrolling properly when I switch between screens.
Can someone review the code and help me whats wrong.
Here is what I have done so far
Controller Code
class CompanyViewController: UIViewController,IndicatorInfoProvider {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var sliderCollectionView: UICollectionView!
#IBOutlet weak var pageView: UIPageControl!
var activityIndicator: UIActivityIndicatorView=UIActivityIndicatorView()
var imgArr = [UIImage(named: "LoginHeader")]
var imgArrTemp = [String]()
var sectionHeaderName = [String]()
var imageArray = [[String]]()
var sectionImage = [String]()
var timer = Timer()
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
pageView.numberOfPages = imgArr.count
pageView.currentPage = 0
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 120
tableView.tableFooterView = UIView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.getBannerData()
DispatchQueue.main.async() {
self.getCenterData()
self.tableView.reloadData()
}
}
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return IndicatorInfo(title: "COMPANY")
}
#objc func changeImage() {
if counter < imgArr.count {
let index = IndexPath.init(item: counter, section: 0)
self.sliderCollectionView.scrollToItem(at: index, at: .centeredHorizontally, animated: true)
pageView.currentPage = counter
counter += 1
} else {
counter = 0
let index = IndexPath.init(item: counter, section: 0)
self.sliderCollectionView.scrollToItem(at: index, at: .centeredHorizontally, animated: true)
pageView.currentPage = counter
counter = 1
}
}
func getCenterData(){
let appId = LocalStorage.getStringDataFromLocalStorage(key:Constants.APPID)
let token = "Bearer "+LocalStorage.getStringDataFromLocalStorage(key: Constants.TOKEN)
if let url = URL(string: Constants.EXPERIENCE_CENTER+appId){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(token, forHTTPHeaderField:"Authorization")
APIManager.sharedInstance.getCall(request: request){
(data) in
if(!data.isEmpty){
do{
let _object = try JSONDecoder().decode(ExperienceCenterModel.self, from: Data(data.utf8))
let data = try JSONEncoder().encode(_object.practice)
let jsonString = String(data: data, encoding: .utf8)!
LocalStorage.saveStringDataInLocalStorage(key: Constants.PRACTICE, value: jsonString)
let technology = try JSONEncoder().encode(_object.technology)
let technologyJsonString = String(data: technology, encoding: .utf8)!
LocalStorage.saveStringDataInLocalStorage(key: Constants.TECTNOLOGY, value: technologyJsonString)
for sectionHeader in _object.practice.data{
self.sectionHeaderName.insert(sectionHeader.practiceName!, at: self.sectionHeaderName.count)
self.sectionImage.removeAll()
for images in sectionHeader.experience{
let url = images.experienceImage.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
self.sectionImage.insert(url!, at: self.sectionImage.count)
}
self.imageArray.insert(self.sectionImage, at: self.imageArray.count)
}
self.tableView.reloadData()
}catch{
let nsError = error as NSError
print(nsError.localizedDescription)
}
}
}
}
}
func getBannerData(){
let defaults = UserDefaults.standard
let appId = defaults.string(forKey: Constants.APPID)
let token = "Bearer "+defaults.string(forKey: Constants.TOKEN)!
if let url = URL(string: Constants.EXPERIENCE_BANNER_CENTER+appId!){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(token, forHTTPHeaderField:"Authorization")
APIManager.sharedInstance.getCall(request: request){
(data) in
if(!data.isEmpty){
do{
let _object = try JSONDecoder().decode(SliderModel.self, from: Data(data.utf8))
self.imgArr.removeAll()
for images in _object.data{
let url = images.experience_image.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let imageUrlString = url
let imageUrl:URL = URL(string: imageUrlString!)!
// self.imgArrTemp.insert(url!, at: self.imgArrTemp.count)
// self.pageView.numberOfPages = self.imgArrTemp.count
DispatchQueue.main.async() {
let imageData:NSData = NSData(contentsOf: imageUrl)!
self.imgArr.insert(UIImage(data: imageData as Data), at: self.imgArr.count)
self.pageView.numberOfPages = self.imgArr.count
}
}
self.stopActivityIndicator()
}
catch{
self.stopActivityIndicator()
self.showAlertMessage(alertMessage: Constants.COMMON_ERROR)
}
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(self.changeImage), userInfo: nil, repeats: true)
}
}
}
}
}
func startActivityIndicator(){
activityIndicator.center = CGPoint(x: 180, y: -40)
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func stopActivityIndicator(){
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
func showAlertMessage(alertMessage:String){
self.stopActivityIndicator()
let alert = UIAlertController(title: "Alert", message: alertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
switch action.style{
case .default:
print("default")
case .cancel:
print("cancel")
case .destructive:
print("destructive")
}}))
self.present(alert, animated: true, completion: nil)
}
}
extension CompanyViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if(self.imgArr.count > 0){
if let vc = cell.viewWithTag(111) as? UIImageView {
if(self.imgArr.count == indexPath.row){
vc.image = self.imgArr[indexPath.row-1]
}else{
vc.image = self.imgArr[indexPath.row]
}
}
}
return cell
}
}
extension CompanyViewController: 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 {
let size = sliderCollectionView.frame.size
return CGSize(width: size.width, height: size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
}
extension CompanyViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if self.sectionHeaderName.count>0 {
return self.sectionHeaderName[section]
}else{
return ""
}
}
//NUMBER OF SECTION WE WANT IN A TABLE
func numberOfSections(in tableView: UITableView) -> Int {
return self.sectionHeaderName.count
}
/*NUMNBER OF ROWS IN A SECTION*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 130
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as? TableViewCell else {fatalError("Unable to create table view cell")}
cell.cellDataURI = self.imageArray[indexPath.section]
cell.collectionView.reloadData()
return cell
}
}
Code for Table View Cell
class TableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var cellData = [UIImage]()
var cellDataURI = [String]()
lazy var imageCache: NSCache<NSString,UIImage> = {
return NSCache<NSString,UIImage>.init()
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.cellDataURI.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as? CollectionViewCell
let url = cellDataURI[indexPath.row]
guard let image = self.imageCache.object(forKey: NSString(string: url))else{
return loadImage(url: URL(string: url)!, cell: cell!, indexPath: indexPath)
}
cell?.imageView.image = image//cellDataURI[indexPath.row]
return cell!
}
func loadImage(url: URL,cell: CollectionViewCell, indexPath: IndexPath) -> CollectionViewCell{
URLSession.shared.dataTask(with: url){
(rawData, _, _) in
guard let data = rawData else { return }
guard let image = UIImage.init(data: data) else{
return
}
self.imageCache.setObject(image, forKey: NSString(string: url.absoluteString) )
DispatchQueue.main.async {
guard let visCell = self.collectionView.cellForItem(at: indexPath) as? CollectionViewCell else{ return }
visCell.imageView.image = image
}
}.resume()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
let size = CGSize(width: 150, height: 300)
return size
}
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
}
Any help would be appreciated
You can use dispatch group to run multiple apis and get notification when all tasks are completed. Then reload tableview
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
firstApiCall(completion:{
dispatchGroup.leave()
})
dispatchGroup.enter()
secondApiCall(completion:{
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .main) {[weak self] in
print("All Data fetched")
self?.tableView.reloadData()
}
want to add a UICollectionView inside UITableView by downloading data from firebase to show
Now I'm done with the interface part, but I'm stuck in the problem, can't bring data from firebase to show in the UICollectionView.
I can't run collectionView.reloadData() because the UICollectionView is different in class, how should I fix it?
func showImageRewardData(rewardID:String) {
let databaseRef = Database.database().reference().child("reward").child(rewardID).child("rewardImage")
databaseRef.observe(DataEventType.value, with: { (Snapshot) in
if Snapshot.childrenCount>0{
self.rewardDataArr.removeAll()
for rewardImage in Snapshot.children.allObjects as! [DataSnapshot]{
let rewardObject = rewardImage.value as? [String: AnyObject]
if(rewardObject?["imageURL"] != nil){
let imageUrl = rewardObject?["imageURL"]
let Data = rewardDetailClass(rewardImage: imageUrl as? String)
self.rewardDataArr.insert(Data, at: 0)
}
YOURVIEWCONTROLLER.SWIFT
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let CellIdentifier: String = "Cell_NotesList"
var cell: Cell_NotesList? = (tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as? Cell_NotesList)
if cell == nil {
let topLevelObjects: [Any] = Bundle.main.loadNibNamed("Cell_NotesList", owner: nil, options: nil)!
cell = (topLevelObjects[0] as? Cell_NotesList)
cell?.selectionStyle = .none
}
cell?.reloadCollectionView(arr: arrofofthumbImages)
return cell!
}
YourCell.SWIFT
class Cell_NotesList: UITableViewCell {
var imagesArr = NSMutableArray()
override func awakeFromNib() {
collectionView.register(UINib.init(nibName: "cell_ImageCollection", bundle: nil), forCellWithReuseIdentifier: "cell_ImageCollection")
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension Cell_NotesList : UICollectionViewDataSource {
func reloadCollectionView(arr:NSMutableArray) {
imagesArr = arr
collectionView.reloadData()
self.layoutIfNeeded()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imagesArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : cell_ImageCollection? = collectionView.dequeueReusableCell(withReuseIdentifier: "cell_ImageCollection", for: indexPath) as? cell_ImageCollection
//YOUR CODE HERE
return cell!
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
extension Cell_NotesList : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 120.0, height: 120.0)
}
}
extension UICollectionViewCell {
var indexPath: IndexPath? {
return (superview as? UICollectionView)?.indexPath(for: self)
}
}
In class that extends UICollectionView, put one setter method like
func setData(data: String){
// Update your cell view here like
Label.text = data
}
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.
I have two different cell type classes and i'm trying to make the first indexpath with the class PayNowCell, have it remain displayed. While the other cells are of class CheckOutCell. Now the problem is in my func numberofitemsinsection. Currently i'm using return checkout.count but it missing the PayNowCell when view loads. If i make it return checkout.count+1 to always have the PayNowCell available; my program crashes giving me the error index out of bounds. The array checkout is a global var. Can someone explain why and provide a fix? Been stuck on this for a while. Code Below.
class CheckoutController: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var inventoryTabController: InventoryTabController?
override init(frame: CGRect){
super.init(frame: frame)
setupViews()
NotificationCenter.default.addObserver(forName: .arrayValueChanged, object: nil, queue: OperationQueue.main) { [weak self] (notif) in
self?.collectionView.reloadData()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func loadItems() -> [Item]? {
return NSKeyedUnarchiver.unarchiveObject(withFile: Item.ArchiveURL.path) as? [Item]
}
func saveItems() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(checkout, toFile: Item.ArchiveURL.path)
if !isSuccessfulSave {
print("Failed to save items...")
}
}
func addItem(item: Item) {
items.append(item)
collectionView.reloadData()
}
func editItem(item: Item, index: Int) {
items[index] = item
collectionView.reloadData()
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Identify which segue is occuring.
if segue.identifier == "ShowDetail" {
let itemDetailViewController = segue.destination as! AddItemController
// Get the cell that generated this segue.
if let selectedItemCell = sender as? InventoryCell {
let indexPath = collectionView.indexPath(for: selectedItemCell)!
let selectedItem = items[indexPath.row]
itemDetailViewController.item = selectedItem
}
}
else if segue.identifier == "AddItem" {
print("Adding new meal.")
}
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
cv.backgroundColor = UIColor.rgb(r: 247, g: 247, b: 247)
return cv
}()
let cellId = "cellId"
let paynow = "paynow"
func setupViews() {
backgroundColor = .brown
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
collectionView.indicatorStyle = UIScrollViewIndicatorStyle.white
collectionView.register(PayNowCell.self, forCellWithReuseIdentifier: paynow)
collectionView.register(CheckoutCell.self, forCellWithReuseIdentifier: cellId)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return checkout.count //init number of cells
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "paynow", for: indexPath) as! PayNowCell //init cells
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! CheckoutCell //init cells
print("Printing this \(checkout.count)")
cell.item = checkout[indexPath.item]
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//let item = items[indexPath.item]
//inventoryController?.showItemDetailForItem(item: item, index: indexPath.item)
print("selected")
print(indexPath.item)
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item == 0 {
return CGSize(width:frame.width, height: 100) //each cell dimensions
}else{
return CGSize(width:frame.width, height: 150) //each cell dimensions
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
It's better to re-arrange your collection view to have 2 sections:
Section 0: PayNow cell (only 1 cell)
Section 1: Checkout cells (using checkout array list)
Then you don't have any confusion about the indexPath.item issue.
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return section == 0 ? 1 : checkout.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "paynow", for: indexPath) as! PayNowCell //init cells
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! CheckoutCell //init cells
print("Printing this \(checkout.count)")
cell.item = checkout[indexPath.item]
return cell
}
}
You need to subtract 1 from your cellForRow function. Since you are adding the PayNowCell you are adding one extra indexPath, but currently, you are using the default indexPath given by the dataSource function. That index will always be one higher than the count of your items. By subtracting 1 from the indexPath, you will be back in sync with your itemsArray, taking into account the PayNowCell.
cell.item = checkout[indexPath.item - 1]
When the user selects a cell in the collection view, it pushes to a new view controller for that cell.
The problem is that when the user swipes back, the collection view controller runs viewDidLoad() instead of viewDidAppear(). This causes the whole collection view to reload and go back up to the top (first cell) and the user has to scroll all the way back down to get to where they were before.
Does anyone know why this is happening??
import UIKit
import FirebaseStorage
class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let reuseIdentifier = "PostCell"
var post: [Post] = [Post]()
var imageURLs: [URL] = [URL]()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.hidesBarsOnSwipe = true
collectionView?.backgroundColor = UIColor(red: 0.91, green: 0.91, blue: 0.91, alpha: 1.00)
// Uncomment the following line to preserve selection between presentations
self.clearsSelectionOnViewWillAppear = false
// Register cell classes
collectionView?.register(PostCell.self, forCellWithReuseIdentifier: reuseIdentifier)
//setupHorizontalBar()
setupCollectionView()
// Get all of the posts
loadPosts()
print("loaded")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
print("appeared")
collectionView.index
}
func setupCollectionView() {
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 0
}
}
func loadPosts() {
postRef.observe(.value, with: { (snapshot) in
for eachPost in snapshot.value as! [String: Any] {
let dict: Dictionary<String, Any> = [eachPost.key: eachPost.value]
let post = Post(postDictionary: dict)
print(post)
self.posts.append(post)
self.collectionView?.reloadData()
}
})
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PostCell
//cell.backgroundColor = .brown
let post = posts[indexPath.row]
cell.nameLabel.text = post.name
let details = "\n\(post.address!)\n\n\(post.time!) \(post.date!)"
cell.postDetailTextView.text = details
if let imageURL = post.image {
print(imageURL)
cell.postImageView.sd_setImage(with: URL(string: imageURL))
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = (self.view.frame.width - 20) * 9 / 16
return CGSize(width: self.view.frame.width, height: height + 5 + 140)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
When didSelectItemAt is called, I want the navigation controller to push to another view controller for the cell selected.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
let detailVC = DetailViewController()
self.navigationController?.pushViewController(detailVC, animated: true)
}
}