I'm trying to retrieve data from firebase, but I need the data to show up in a collectionView that's inside a tableViewCell (which makes it a bit confusing).
I have three files. FeedTableViewCell, FeedCollectionViewCell, FeedViewController.
Normally I would just call the retrieve function in the FeedViewController/ViewDidLoad function. But since I need it in the collectionView its not working in this case. Where can I add the function so I can use it in my collectionView? Or how can I reformat the code?
Code below isn't working (Fatal error in FeedTaabelViewCell file when run). I've tried adding the retrieve function inside FeedTableViewCell, but there isn't a ViewDidLoad function so its not working. The best I can come up with is to leave it in the main FeedViewController file, but then how do I call it in FeedTableViewCell file?
class FeedTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
var refArtists: DatabaseReference!
var userList = [UserModel]()
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
extension FeedTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FeedCollectionViewCell
let artist: UserModel
//ERROR: INDEX OUT OF RANGE
artist = userList[indexPath.row]
cell.nameLabel.text = artist.name
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemWidth = collectionView.bounds.width
let itemHeight = collectionView.bounds.height
return CGSize(width: itemWidth, height: itemHeight)
}
}
class FeedCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var passionLabel: UILabel!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var feedCellImage: UIImageView!
}
class FeedViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var bounds = UIScreen.main.bounds
return bounds.size.height - (self.tabBarController?.tabBar.frame.size.height)!
}
var refArtists: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
refArtists = Database.database().reference().child("users");
//observing the data changes
refArtists.observe(DataEventType.value, with: { [self] (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
userList.removeAll()
//iterating through all the values
for artists in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let artistObject = artists.value as? [String: AnyObject]
let userName = artistObject?["name"]
let userId = artistObject?["id"]
let userInterest = artistObject?["interest"]
//creating artist object with model and fetched values
let artist = UserModel(id: userId as! String?, name: userName as! String?, interest: userInterest as! String?)
//appending it to list
userList.append(artist)
}
}
})
}
class FeedViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
cell.userList = userList
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var bounds = UIScreen.main.bounds
return bounds.size.height - (self.tabBarController?.tabBar.frame.size.height)!
}
Related
I have a tableView with several cells (created by MVVM-architecture).
In ViewController I fill my tableView like this:
tbv.registerCells(
withModels:
FirstViewModel.self,
SecondViewModel.self,
ThirdViewModel.self)
My task is to put my TableView in one cell of CollectionView. I thought l have to create CollectionView in my ViewController, after it create CollectionViewCell and CollectionCellViewModel, but how to do it exactly I don't understand.
If you know, how to make it, help.
How I have several tableviews in collection views in one of my apps. First I have a view controller in which I build my collection view. As usually proposed in the new design guidelines, I have the Collection View delegate and data source in an extension of this view controller.
In your view controller you define a delegate and data source for your table view. Preferably, this is a different class. I would not have the tableview data source and delegate also in the same view controller as your collection view.
class WorkoutSettingsViewController: UIViewController, LoadWorkoutSettings {
//MARK: - Properties
//Used variables
//Used constants
private let settingsDelegate = SettingsTableViewDelegate()
The extension would then look like this.
extension WorkoutSettingsViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
//Whatever sets your sections
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//Whatever sets your rows per section
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Workout Settings", for: indexPath) as! SettingsCollectionViewCell
settingsDelegate.workoutTitleLabel = [countdown, mainView, spokenMessage]
settingsDelegate.mainContentLabel = getSettingsContent()
cell.settingsTableView.delegate = settingsDelegate
cell.settingsTableView.dataSource = settingsDelegate
cell.settingsTableView.reloadData()
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
//Whatever you need as header or footer
}
The delegate does exactly what you would like the table view data source and delegate to do.
class SettingsTableViewDelegate: NSObject, UITableViewDataSource, UITableViewDelegate {
//MARK: - Properties
//Used variables
var workoutTitleLabel = [String]()
var mainContentLabel = [String]()
var selectedSetting: ((Int) -> ())? = .none
private var secondaryContentLabel = [String]()
//Used constants
private let onTitle = NSLocalizedString("ON", comment: "Localized on title")
private let offTitle = NSLocalizedString("OFF", comment: "Localized off title")
private let fontColorBlack = UIColor(red: 20.0/255.0, green: 20.0/255.0, blue: 19.0/255.0, alpha: 1.0)
private let fontColorRed = UIColor(red: 255.0/255.0, green: 96.0/255.0, blue: 89.0/255.0, alpha: 1.0)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
workoutTitleLabel.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Settings Cell") as! WorkoutTableViewCell
cell.workoutTitle.text = workoutTitleLabel[indexPath.row]
cell.mainContent.text = mainContentLabel[indexPath.row]
cell.secondaryContent.text = ""
(mainContentLabel[indexPath.row] == offTitle) ? (cell.mainContent.textColor = fontColorRed) : (cell.mainContent.textColor = fontColorBlack)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
selectedSetting?(indexPath.row)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
61
}
}
Your collection view cell should look like this.
class SettingsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var settingsTableView: UITableView!
}
This should then work. If you need to have a callback from the table view delegate / data source to your view controller managing your collection view, you can use a closure. In the example table view delegate the closure is called selectedSettings. In your view controller in viewDidLoad you define the call back for instance like this:
override func viewDidLoad() {
super.viewDidLoad()
settingsDelegate.selectedSetting = { [unowned self] selection in
startSettingsMenu(for: selection)
}
}
The result looks like this.
Kind regards,
MacUserT
In Tableview each row you can load UITableViewCell with pass collectionviewdata
//View Controller
var collectionView1Data = ["cell1", "cell2"]
var collectionView2Data = ["cell1", "cell2"]
//UITableviewDelegate Datasource
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//.....
if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellID") as? TableviewCell
cell.collectionData = collectionView1Data /// Collectionviewdata
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellID") as? TableviewCell
cell.collectionData = collectionView2Data
return cell
}
}
==============================
Each Tableviewcell contains CollectionView
//UITableViewCell
class TableviewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
var collectionData: [String]? {
didSet {
guard collectionData != nil else {
return
}
collectionView.reloadData()
}
}
override func awakeFromNib() {
super.awakeFromNib()
collectionView.register(UINib(nibName: "collectionViewCell", bundle: nil), forCellWithReuseIdentifier: "collectionViewCell")
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension TableviewCell: UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
collectionData.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as? collectionViewCell
cell...
return cell
}
}
I have a table view with collection view inside table rows.
structure is next:
MainViewController.swift:
class MainViewController: UIViewController {
#IBOutlet weak var customTable: UITableView!
func callSegue() {
performSegue(withIdentifier: "customSegue", sender: self)
}
override func viewDidLoad() {
customTable(UINib(nibName: "CustomTableCell", bundle: nil), forCellReuseIdentifier: "TipsTableCell")
}
}
extension MainViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableCell", for: indexPath) as! CustomTableCell
//Fill cell with my data
return cell
}
}
CustomTableCell.swift
class CustomTableCell.swift: UITableViewCell {
#IBOutlet var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(UINib.init(nibName: "CustomTableCell", bundle: nil), forCellWithReuseIdentifier: "CustomTableCell")
}
}
extension CustomTableCell: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomTableCell", for: indexPath) as! CustomTableCell
cell.label1.text = dataArray[indexPath.item]
return cell
and my CustomCollectionvCell.swift
class CustomCollectionvCell: UICollectionViewCell {
#IBOutlet weak var label1: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
I need something like this:
I need to call "callSegue" func in MainViewController when I tapped at cell where label1.text == "Something".
Use closures to solve that.
Add a closure in CustomTableCell and call it when the collectionViewCell is tapped in collectionView(_:didSelectItemAt:) method, i.e.
class CustomTableCell: UITableViewCell {
var handler: (()->())?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.handler?()
}
}
In MainViewController, set the closure while dequeuing CustomTableCell in tableView(_:cellForRowAt:) method, i.e.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableCell", for: indexPath) as! CustomTableCell
cell.handler = {[weak self] in
self.callSegue() //here.....
}
return cell
}
Also cross-check if you have a segue with identifier customSegue in your storyboard.
In your case, you have to implement delegate from CustomTableCell.swift and use in MainViewController.swift
// MainViewController.swift:
class MainViewController: UIViewController {
#IBOutlet weak var customTable: UITableView!
func callSegue() {
performSegue(withIdentifier: "customSegue", sender: self)
}
override func viewDidLoad() {
customTable(UINib(nibName: "CustomTableCell", bundle: nil), forCellReuseIdentifier: "TipsTableCell")
}
}
extension MainViewController: UITableViewDataSource, collectionViewCellTapped {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableCell", for: indexPath) as! CustomTableCell
//Fill cell with my data
return cell
}
func cellTapped(_ text: String) {
if let text = dataArray[indexPath.item], text == "Something" {
callSegue()
}
}
}
// CustomTableCell.swift
protocol collectionViewCellTapped {
func cellTapped(_ text: String)
}
class CustomTableCell : UITableViewCell {
#IBOutlet var collectionView: UICollectionView!
var delegate: collectionViewCellTapped!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(UINib.init(nibName: "CustomTableCell", bundle: nil), forCellWithReuseIdentifier: "CustomTableCell")
}
}
extension CustomTableCell: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomTableCell", for: indexPath) as! CustomTableCell
cell.label1.text = dataArray[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let delegate = self.delegate {
delegate.cellTapped(text)
}
}
}
I want to pushViewController and show detail about that cell!
Swift5
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView.delegate = self
myTableView.dataSource = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return ViewController.typeOfDishes.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return ViewController.typeOfDishes[section]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.myTableView.dequeueReusableCell(withIdentifier: "MyTableViewCell") as! MyTableViewCell
return cell
}
}
import UIKit
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var myCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
myCollectionView.delegate = self
myCollectionView.dataSource = self
}
}
extension MyTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return ViewController.dishesCountByArray[ViewController.indexOfTableView!]
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
////////////There I want to do PushViewController Action
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.myCollectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as! MyCollectionViewCell
return cell
}
}
I want to normally pu​sh when user taps on collection view cell
Inside here Add a delegate
class MyTableViewCell: UITableViewCell {
weak var delegate:ViewController?
.....
}
Then set it here
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.myTableView.dequeueReusableCell(withIdentifier: "MyTableViewCell") as! MyTableViewCell
cell.delegate = self
return cell
}
Now you can push here
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondID") as! SecondViewController
self.delegate?.navigationController?.push////
}
I can't call performseguewithidentifier in Infinite3TableViewCell class inside. i knew it is UITableViewCell.
So, How can call performseguewithidentifier method in Infinite3TableViewCell?
I want to select a cell then present/segue to new ViewController.
// MARK: - UITableViewDataSource, UITableViewDelegate
final class BuyTableViewController: UIViewController {
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
}
}
// MARK: - UITableViewDataSource, UITableViewDelegate
extension BuyTableViewController: UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0, 1, 2, 3, 4: return 1
default: return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "Infinite0TableViewCell", for: indexPath)
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: InfiniteTableViewCell.identifier) as! InfiniteTableViewCell
return cell
case 2:
let cell = tableView.dequeueReusableCell(withIdentifier: Infinite2TableViewCell.identifier) as! Infinite2TableViewCell
return cell
case 3:
let cell = tableView.dequeueReusableCell(withIdentifier: Infinite3TableViewCell.identifier) as! Infinite3TableViewCell
return cell
case 4:
let cell = tableView.dequeueReusableCell(withIdentifier: Infinite4TableViewCell.identifier) as! Infinite4TableViewCell
return cell
default:
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.section {
case 0: return 80
case 1: return 150
case 2: return 120
case 3: return 400
case 4: return 500
default: return 0
}
final class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imgImage: UIImageView!
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var imgEffect: UIVisualEffectView!
final class Infinite3TableViewCell: UITableViewCell {
static let identifier = "Infinite3TableViewCell"
var nameArray = ["Women's Clothing", "Beauty & Personl Care", "Women's Bags", "Men's Clothing", "Mobile & Gadgets", "Watches", "Home", "Toys", "Home Appliances"]
var imgName = [UIImage(named: "girl"), UIImage(named: "makeup"), UIImage(named: "bags"), UIImage(named: "clothing"), UIImage(named: "mobile"), UIImage(named: "watches"), UIImage(named: "living"), UIImage(named: "toys"), UIImage(named: "tools")]
extension Infinite3TableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return nameArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as! CollectionViewCell
cell.imgImage.image = imgName[indexPath.row]
cell.lblName.text! = nameArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("didSelectItemAt: \(indexPath.item)")
performseguewithidentifier should be called from a UIViewController or its subclass. You should let the UIViewController know and call it.
I have tableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellTime", for: indexPath) as! TimeViewCell
let showHour = self.infoWripper.sorted(by: { $0.hour < $1.hour })[indexPath.row]
cell.labelTime.text = showHour.showHour()
cell.dataForShow = showHour.showFullTime()
return cell
}
Cell of TableView contents UICollectionView with cells.
class TimeViewCell: UITableViewCell {
#IBOutlet weak var labelTime: UILabel!
var dataForShow = [String]()
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension TimeViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataForShow.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CustomCollectionCell
cell.labelCollection.text = dataForShow[indexPath.row]
//heightForCell = collectionView.contentSize.height
return cell
}
}
class CustomCollectionCell: UICollectionViewCell {
#IBOutlet weak var labelCollection: UILabel!
}
When I open VC with table - view is OK, but when I begin scrolling - I see, that data not correct (reuse), but I can't find my mistake.
before scroll:
after:
You need to reload the collection view in your cell.
Try this:
class TimeViewCell: UITableViewCell {
....
var dataForShow = [String]() {
didSet {
self.collectionView?.reloadData()
}
}