i have a table view and i have placed a collection view inside the cell, i'm getting data from API and that data i'm passing to table view and collection view but when i run the app it crashes with this error,
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Lawon.KnowledgeVC collectionView:numberOfItemsInSection:]: unrecognized selector sent to instance 0x7fa90af0cbc0
My code for table view,
extension KnowledgeVC : UITableViewDelegate,UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return categoryArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categoryArray.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 35
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = Bundle.main.loadNibNamed("KnowledgeHeaderTVC", owner: self, options: nil)?.first as! KnowledgeHeaderTVC
headerView.categoryNameLbl.text = categoryArray[section].catName
headerView.articlesLbl.text = "\(categoryArray[section].blogArray.count)" + "articles"
return headerView
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = knowledgeTableView.dequeueReusableCell(withIdentifier: "KnowledgeCell", for: indexPath) as! KnowledgeDetailTVC
cell.categoryArray = categoryArray
return cell
}
}
This is my table view cell class where i have mad outlet for collection view and populating data in it and calling its delegate and datasource,
class KnowledgeDetailTVC: UITableViewCell,UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var categoryCollectionView : UICollectionView!
var categoryArray = [Category]()
override func awakeFromNib() {
super.awakeFromNib()
self.categoryCollectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(categoryArray[section].blogArray.count)
return categoryArray[section].blogArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "familyCell", for: indexPath) as! FamilyCVC
cell.categoryLbl.text = categoryArray[indexPath.section].blogArray[indexPath.row].articleTitle
let imageUrl = categoryArray[indexPath.section].blogArray[indexPath.row].imageUrl!
print(imageUrl)
cell.categoryImage.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage(named: "person.jpeg"))
// cell.bgView.layer.masksToBounds = true
// cell.bgView.layer.shadowColor = UIColor.black.cgColor
// cell.bgView.layer.shadowOpacity = 3
// cell.bgView.layer.shadowOffset = CGSize(width: -1, height: 1)
// cell.bgView.layer.shadowRadius = 2
// cell.bgView.layer.shouldRasterize = true
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
You may set the delegate & dataSource of the collectionView in the prototype cell to the KnowledgeVC in IB while implementation is inside the cell , but you should set them in awakeFromNib
self.categoryCollectionView.delegate = self
self.categoryCollectionView.dataSource = self
self.categoryCollectionView.reloadData()
Also it's better to refresh it inside cellForRowAt to avoid dequeuing problems
cell.categoryArray = categoryArray
cell.categoryCollectionView.reloadData()
There is no compile time error thrown in that case and that is a big problem of setting the delegate and dataSource in IB
Related
I have a CollectionView that is scrolling horizontally. I have custom CollectionViewCells that have inside a TableView with three cells. It looks like this:
[![Here is a example of how it looks][1]][1]
This is the main ViewController where I have the CollectionView
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.viewModel.numberOfItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ItemCell.reuseIdentifier, for: indexPath) as! ItemCell
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedItem = self.viewModel.items[indexPath.item]
}
}
And this is the ItemCell code that contains the tableview
class ItemCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func awakeFromNib() {
super.awakeFromNib()
self.tableView.delegate = self
self.tableView.dataSource = self
}
override func layoutSubviews() {
super.layoutSubviews()
self.tableView.allowsMultipleSelection = true
self.tableView.register(UINib(nibName: "DailySelectionViewCell", bundle: nil), forCellReuseIdentifier: DailySelectionViewCell.reuseIdentifier)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 20
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: DailySelectionViewCell.reuseIdentifier, for: indexPath) as! DailySelectionViewCell
switch indexPath.section {
case 0:
cell.descriptionLabel.text = "Item1"
case 1:
cell.descriptionLabel.text = "Item2"
default:
cell.descriptionLabel.text = "Item3"
}
return cell
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect.zero)
view.backgroundColor = UIColor.clear
return view
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
I want to click on the TableView cells that are inside the CollectionView cells. When I want to click on a TableViewCell, the touches are only registered on the CollectionViewCell and I can't click on any of the tableview cells inside the CollectionViewCell.
By default didSelectRowAt must work.
As an assumption DailySelectionViewCell may contains some views with user interaction, namely subclassed from UIControl: UIButton, UIDatePicker, UISegmentedControl and so on. In this case didSelectRowAt not work and this is correct behavior.
I have a category list and each category has a sub category say for example category one might have 10 subcategories and category two may have 5 subcategories.
I have implemented an UICollectionView inside an UITableView which works perfect but the only problem is how can I display the subcategories inside a particular Category
I have these classes
this is my CategoriesCollection class
class CategoriesCollection: UICollectionView,UICollectionViewDataSource,UICollectionViewDelegate {
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//horizontal items number
return imageData.count //this should be the subcategories count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "subcategoriesCell", for: indexPath) as! SubcategoriesCollectionCell
cell.subcategoryImg.image = imageData[indexPath.row]
cell.subcategoryName.text = imageName[indexPath.row]
return cell
}
}
and here my table view class
class HomeTable: UITableView,UITableViewDelegate,UITableViewDataSource{
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
let cellSpacingHeight: CGFloat = 0
func numberOfSections(in tableView:UITableView)->Int{
return category.count
}
func tableView(_ tableView:UITableView,numberOfRowsInSection section:Int)->Int{
return 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return cellSpacingHeight
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clear
return headerView
}
func tableView(_ tableView:UITableView,cellForRowAt indexPath: IndexPath)->UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as! CategoryCell
//setting the category title here
return cell
}
}
All you need is to redesign your tableviewCell add a label and under label add collectionview once you are adding data into collectionview you just need to add category name to the label and all subcategories will be in collectionview under that label like.
Hope you understand what i tried to tell you if not feel free to ask.
Thanks
i have used collection view inside the table view and populate data inside the collection view coming from backend. Now when i reload the data it does not shows me UI in a way i'm trying to show, i want my UI to look like this,
But when i run my app and open the VC my table view looks like that,
Data is not coming in horizontal way and its duplicating the cell of collection view. This is my code for Collection view to populate data inside it.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categoryArray[section].blogArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "familyCell", for: indexPath) as! FamilyCVC
cell.categoryLbl.text = categoryArray[indexPath.section].blogArray[indexPath.row].articleTitle
let imageUrl = categoryArray[indexPath.section].blogArray[indexPath.row].imageUrl!
print(imageUrl)
cell.categoryImage.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage(named: "person.jpeg"))
cell.bgView.layer.masksToBounds = true
cell.bgView.layer.shadowColor = UIColor.black.cgColor
cell.bgView.layer.shadowOpacity = 3
cell.bgView.layer.shadowOffset = CGSize(width: -1, height: 1)
cell.bgView.layer.shadowRadius = 2
cell.bgView.layer.shouldRasterize = true
return cell
}
This is the code for table view holding collection view ,
func numberOfSections(in tableView: UITableView) -> Int {
return categoryArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categoryArray.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 35
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = Bundle.main.loadNibNamed("KnowledgeHeaderTVC", owner: self, options: nil)?.first as! KnowledgeHeaderTVC
headerView.categoryNameLbl.text = categoryArray[section].catName
headerView.articlesLbl.text = "\(categoryArray[section].blogArray.count)" + "articles"
return headerView
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = knowledgeTableView.dequeueReusableCell(withIdentifier: "KnowledgeCell", for: indexPath) as! KnowledgeDetailTVC
//cell.categoryArray = categoryArray
cell.categoryCollectionView.delegate = self
cell.categoryCollectionView.dataSource = self
cell.categoryCollectionView.reloadData()
return cell
}
I want to show my data in UI like the first image in my question.
You may need
1-
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1 // change this to 1
}
2-
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = knowledgeTableView.dequeueReusableCell(withIdentifier: "KnowledgeCell", for: indexPath) as! KnowledgeDetailTVC
cell.categoryCollectionView.tag = indexPath.section // add this
cell.categoryCollectionView.delegate = self
cell.categoryCollectionView.dataSource = self
cell.categoryCollectionView.reloadData()
return cell
}
3-
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categoryArray[collectionView.tag].blogArray.count // use tag here
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "familyCell", for: indexPath) as! FamilyCVC
cell.categoryLbl.text = categoryArray[collectionView.tag].blogArray[indexPath.row].articleTitle
let imageUrl = categoryArray[collectionView.tag].blogArray[indexPath.row].imageUrl!
print(imageUrl)
cell.categoryImage.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage(named: "person.jpeg"))
cell.bgView.layer.masksToBounds = true
cell.bgView.layer.shadowColor = UIColor.black.cgColor
cell.bgView.layer.shadowOpacity = 3
cell.bgView.layer.shadowOffset = CGSize(width: -1, height: 1)
cell.bgView.layer.shadowRadius = 2
cell.bgView.layer.shouldRasterize = true
return cell
}
I have collectionView inside TableViewCell.
TableView has 1 section and 4 rows.
I want to figure out which row of tableviewcell is selected when collectionView is Scrolled Horizontally.
I tryed to figure out by putting tableView row in "var nowRow" variable like below code. But It does not work.
How can I find out which row of tableviewcell is selected?
class Home2ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate {
var posts = [Post]()
var nowRow = Int()
override func viewDidLoad() {
// get posts elements here
//posts.append(element).......
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count // 4counts
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = DetailMutiTableViewCell()
let cell = tableView.dequeueReusableCell(withIdentifier: "DetailMutiTableViewCell", for: indexPath) as! DetailMutiTableViewCell
nowRow = indexPath.row
cell.post = posts[nowRow]
cell.collectionView1.delegate = self
cell.collectionView1.dataSource = self
cell.collectionView1.reloadData()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts[nowRow].imageUrls.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MultiImagesCollectionViewCell", for: indexPath) as! MultiImagesCollectionViewCell
let photoUrlString = posts[nowRow].imageUrls[indexPath.item] // ←error occured
let photoUrl = URL(string: photoUrlString)
cell.imageView1.sd_setImage(with: photoUrl)
return cell
}
}
editeditedit
As you have no doubt discovered, interacting with the collectionViews in your table rows does not trigger the tableView method tableView(_:didSelectRowAt:), and tableView.indexPathForSelectedRow is nil because no row has been selected. You need a way to map from the collectionView to the indexPath.row that contains it. This information is known at tableViewCell setup time.
Add a dictionary to your Home2ViewController that maps a UICollectionView to a row Int:
var collectionViewRow = [UICollectionView : Int]()
Add the entries in tableView(_:cellForRowAt:) where you know the row and collectionView:
collectionViewRow[cell.collectionView1] = indexPath.row
Then, anytime you have a collectionView, you can look up its row:
let row = collectionViewRow[collectionView]!
Here's the whole code:
class Home2ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate {
var posts = [Post]()
var collectionViewRow = [UICollectionView : Int]()
override func viewDidLoad() {
// get posts elements here
//posts.append(element).......
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count // 4counts
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = DetailMutiTableViewCell()
let cell = tableView.dequeueReusableCell(withIdentifier: "DetailMutiTableViewCell", for: indexPath) as! DetailMutiTableViewCell
collectionViewRow[cell.collectionView1] = indexPath.row
cell.post = posts[indexPath.row]
cell.collectionView1.delegate = self
cell.collectionView1.dataSource = self
cell.collectionView1.reloadData()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let row = collectionViewRow[collectionView]!
return posts[row].imageUrls.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MultiImagesCollectionViewCell", for: indexPath) as! MultiImagesCollectionViewCell
let row = collectionViewRow[collectionView]!
let photoUrlString = posts[row].imageUrls[indexPath.item] // ←error occured
let photoUrl = URL(string: photoUrlString)
cell.imageView1.sd_setImage(with: photoUrl)
return cell
}
}
Notes:
The force unwrap of the dictionary lookup won't fail because the collectionView will be in there. If you want to unwrap it with if let, you'll need to decide what to do when you don't get a row (which shouldn't happen).
This method could fail if your table allows editing (user can rearrange order of rows or delete rows). If you don't support editing, then this will work fine. To support editing, you could change the dictionary to [UICollectionView : UITableViewCell]. Then, given a UICollectionView, you could look up its cell, and then call let indexPath = tableView.indexPath(for: cell) to get the indexPath and your row is then just indexPath.row.
I have a tableview cell inside which i have added collectionview cell ( for horizontal scrolling).
Now i want to push to other navigation controller on button of any cell of horizontal collectionview. How to do it ? O
Code :
ViewController.swift :
class ViewController: UIViewController {
var categories = ["Action", "Drama", "Science Fiction", "Kids", "Horror"]
}
extension ViewController : UITableViewDelegate { }
extension ViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return categories[section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return categories.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CategoryRow
return cell
}
}
CategoryRow.swift
class CategoryRow : UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
}
extension CategoryRow : UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! VideoCell
cell.button.addTarget(self, action:#selector(ViewController.goToLookAtPage(_:)), for: .touchUpInside)
return cell
}
}
extension CategoryRow : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemsPerRow:CGFloat = 4
let hardCodedPadding:CGFloat = 5
let itemWidth = (collectionView.bounds.width / itemsPerRow) - hardCodedPadding
let itemHeight = collectionView.bounds.height - (2 * hardCodedPadding)
return CGSize(width: itemWidth, height: itemHeight)
}
}
where do i declare goToLookAtPage function ?
VideoCell.swift
class VideoCell : UICollectionViewCell {
#IBOutlet weak var button: UIButton!
}
You need to delclare it
on CategoryRow Class also declare global closure
like
var buttonTapped:((CategoryRow?) -> Void)? = nil
Now we have closure to call
Implement goToLookAtPage as below
func goToLookAtPage(_ sender:UIButton) {
if let btnAction = self.buttonTapped {
btnAction(self)
}
}
Now in your ViewController add following in cellForRowAtIndexPath
cell.buttonTapped = {(cell) -> Void in
//You Got your response , do push in main Thread
}
Hope it helps to you
In your button handler function get the button's position where sender is your button.
let position = sender.convert(CGPoint.zero, to: self.collectionView)
Get the index path for the given position.
let indexPath = self.collectionView?.indexPathForItem(at: position)
Get the model from your data source with the index path.
let model = self.data[indexPath.row]
Now that you have your data model, pass it to your destination and push your view controller or whatever.