I have one ui table view controller, that has four custom cells, each one of them has a custom ui table view cell.
now let's say i have added some buttons to the third custom ui table view cell, and i want to when one button of them clicked to access data (such as labels) that are in the second custom ui table view cell.
how can i do that?
this is a one example of the third custom ui table view cell
class thirdCellc: UITableViewCell {
//here i want to access data that are in another custom ui table view cell
}
keep on mind that i have also a custom ui table view controller like this:
class PageTwoViewController: UITableViewController {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let rowNumber = indexPath.row
switch rowNumber {
case RequestData.numberOfPeople.rawValue:
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentefiers.numberOfPeopleCell.rawValue) as! NumberOfPeopleTableViewCell
return cell
case RequestData.dayOfMeal.rawValue :
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentefiers.dayCell.rawValue) as! DayTableViewCell
return cell
case RequestData.timeOfMeal.rawValue:
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentefiers.timeCell.rawValue) as! TimeTableViewCell
return cell
case RequestData.preferences.rawValue:
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentefiers.preferencesCell.rawValue) as! PreferencesTableViewCell
return cell
default:
return UITableViewCell()
}
}
}
you need to create an outlet from the tableView to the custom class, then you can write code as follows
#IBOutlet weak var tableView: UITableView! //outlet you created
let indexPath = NSIndexPath() //Get index Path of cell you want in tableView
let cell = tableView.cellforRowAtIndexPath(indexPath) as! FirstCell //Or whatever the other cell class was.
cell.textLabel.text //Now you have all the dot properties of whatever cell at indexPath you passed in.
Related
I have a tableview, where each cell is a custom tableview cell. That custom tableview cell contains a tableview and a label.
Lets say, the outer tableview is called MainTableView. Each cell of MainTableView consists another tableview. The problem is , when I select
inner tableview cell one by one the previously selected cell is not get deselected.In first image I have selected cell contains text “Two”. Then In the second image I have selected cell contains text “Five” but perviously selected cell “Two” still in selection mode. I want to deselect the previous cell when select a new one.
I have tried
tableView.deselectRow(at: IndexPath, animated: Bool)
this method inside didSelectRowAt in custom tableviewcell class but it didn’t serve the purpose because the previous indexPath is from seperate tableview. So, how can I deselect the previous one?
to get the correct inner tableView,
Firstly , you should record the outside tableView's cell indexPath, which cell the inner tableView is in.
So your should record two indexPathes
var selectionRowInfo: (lastOutsideIP: IndexPath?, lastInnerIP: IndexPath?)
Secondly, you get the correct tableView, via the outsideTableView.
if the inner table is visible, you shall handle it immediately. through outTableView.indexPathsForVisibleRows
the else condition, you need not handle it. The tableView reuse mechanism will refresh its state.
// pseudo code.
if let lastOut = lastOutsideIP, let visibleRows = outTableView.indexPathsForVisibleRows, visibleRows.contains(lastOut){
let cell = tableView.cellForRow(at: lastOut) as! YourCell
// get the correct inner tableView via the cell
}
Because the inner tableViews are not related to each other. Select the cell of table one, will not affect the selection of cell of table two.
So your should build the connection manually.
Use a property to store the state var lastIndexPath: IndexPath?,
then every time select a indexPath,
// pseudo code.
if let last = lastIndexPath{
tableView.deselectRow(at: last, animated: true)
}
Please notice that, you should find the correct inner tableView, which has the lastIndexPath
The previous answer is along the right lines but has a flaw - it cannot distinguish between tableViews, which is the most important part. Also if the tableViews have different numbers of rows, it risks trying to access a row that doesn't exist and causing a crash.
To track the selected row in two tableViews (tv1 & tv2) you'll need to hold the selected row in each:
var tv1, tv2: UITableView!
var lastRowForTV1, lastRowForTV2: IndexPath?
and then respond to selections by identifying the tableView being used and adjusting the other (this assumes the two tableViews use the same datasource/delegate)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView === tv1 {
lastRowForTV1 = indexPath
if let last = lastRowForTV2 {
tv2.deselectRow(at: last, animated: true)
lastRowForTV2 = nil
}
} else if tableView === tv2 {
lastRowForTV2 = indexPath
if let last = lastRowForTV1 {
tv1.deselectRow(at: last, animated: true)
lastRowForTV1 = nil
}
}
}
I have solved the problem by using the idea of first answer given by dengApro . The idea was to find the correct inner table which contains the previously selected cell.
I have two files one is ViewController.swift that contains the outer tableview MainTableView. Another one is CustomTableViewCell.swift with CustomTableViewCell.xib that contains the custom cell with tableview.
fileprivate var lastSelectedInnerTableView : Int = -1
fileprivate var lastSelectedRow: Int = -1
fileprivate var tableViewList :[UITableView] = []
I have added these 3 variables in CustomTableViewCell.swift file outside the class CustomTableViewCell. lastSelectedSection , lastSelectedRow these 2 variable are used to keep
track of the last selected inner tableview (lastSelectedInnerTableView) and the last selected cell of that inner tableView (lastSelectedRow). tableViewList variable is used to keep the
Inner tableView. Inside awakeFromNib() I have append the created inner tableviews.
override func awakeFromNib() {
super.awakeFromNib()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableViewList.append(self.tableView) // append the created inner tableviews
}
Then inside didSelectRowAt I have deselect the previous one:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if lastSelectedInnerTableView != -1 && lastSelectedRow != -1{
self.oldIndexPath.section = 0
self.oldIndexPath.row = lastSelectedRow
tableViewList[lastSelectedInnerTableView].deselectRow(at: self.oldIndexPath, animated: true)
}
lastSelectedInnerTableView = self.innerTableId
lastSelectedRow = indexPath.row
}
In ViewController.swift I have set innerTableId inside cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell: CustomTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as! CustomTableViewCell
customCell.innerTableId = indexPath.row
customCell.customCellActionDelegate = self
return customCell
}
I have a UITableView with 2 sections each of which contains one cell. These cells contain UICollectionViews. These collection views are of different types. Trying to follow the MVC design pattern, I make my ViewController a data source and a delegate of both UITableView and UICollectionView.
Here is the data source code of my table view:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: recomendationCellId, for: indexPath) as! RecomendationsTableViewCell
cell.collectionView.dataSource = self
cell.collectionView.delegate = self
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: collectionCellId, for: indexPath) as! CollectionsTableViewCell
cell.collectionView.dataSource = self
cell.collectionView.delegate = self
return cell
} else {
return UITableViewCell()
}
}
Now, I need to dequeue cells for the collection view, by using collectionView(_:,cellForItem:) method. Inside of it I need to check the section of the table view to dequeue the right cell.
The problem is that I know that it should have a simple solution but can't figure it out. Do you have any suggestions how this can be achieved?
If collection view is inside cell then handle it with in cell only...so when you are setting cell.collectionView.dataSource = self it means you are setting delegate to current view controller not the cell, means that collection view look for the data source in current view controller.
As you are using MVC its better to implement a datasource and delegate methods inside cell.
So you need to ultimately set cell.collectionView.dataSource = cell as cell will implement data source and delegate methods.
Your MVC pattern won't break even if you confirm to the collection view delegates and datasource methods from your cell itself!
A model is responsible to provide you with data, not the controller.
1. Have a model for displaying your collection view cell data
2. confirm them from your tableview cell itself!
extension yourTableViewCell: UICollectionViewDelegate {
// Respective methods
}
extension yourTableViewCell: UICollectionViewDataSource {
// Respective methods
}
I read similar questions such as how to have multiple collection view in multiple table view cells and I connected my collection views cells and use identifier names for them but I don't know why I receive this Error:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier extera_infoCollectionViewCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
* First throw call stack:
**Remember that I read Similar questions and the first table view cell with collection view working well and the problem is for second one **
here is my code for main view controller that has a table view and the table view has two cells
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == fieldOfActivityCell().fieldofActivitiesCollectionView {
let fullfields : String = self.adv.resultValue[0].work_field!
let fullfieldsArr : [String] = fullfields.components(separatedBy: ",")
print(fullfieldsArr)
return fullfieldsArr.count
} else {
let extera_infofields : String = self.adv.resultValue[0].extera_info!
let extera_infofieldsArr : [String] = extera_infofields.components(separatedBy: ",")
print(extera_infofieldsArr)
return extera_infofieldsArr.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == fieldOfActivityCell().fieldofActivitiesCollectionView {
let fieldsCells = collectionView.dequeueReusableCell(withReuseIdentifier: "fieldOfActivityCollectionViewCell", for: indexPath) as! fieldOfActivityCollectionViewCell
let fullfields : String = self.adv.resultValue[0].work_field!
let fullfieldsArr : [String] = fullfields.components(separatedBy: ",")
fieldsCells.title.text = fullfieldsArr[indexPath.row]
return fieldsCells
}
else {
let extera_infoCells = collectionView.dequeueReusableCell(withReuseIdentifier: "extera_infoCollectionViewCell", for: indexPath) as! extera_infoCollectionViewCell
let extera_info : String = self.adv.resultValue[0].extera_info!
let extera_infoArr : [String] = extera_info.components(separatedBy: ",")
extera_infoCells.infoText.text = extera_infoArr[indexPath.row]
return extera_infoCells
}
}
and here is the table view codes in same view controller:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let fieldCell = self.showAdvTableView.dequeueReusableCell(withIdentifier: "fieldOfActivityCell", for: indexPath) as! fieldOfActivityCell
return fieldCell
} else {
let fieldCell = self.showAdvTableView.dequeueReusableCell(withIdentifier: "extera_infoCell", for: indexPath) as! extera_infoCell
return fieldCell
}
here is table view first cell class:
class fieldOfActivityCell: UITableViewCell {
#IBOutlet weak var fieldofActivitiesCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
if let flowLayout = fieldofActivitiesCollectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize.init(width: 1.0, height: 1.0) }
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension fieldOfActivityCell {
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDelegate & UICollectionViewDataSource>
(_ dataSourceDelegate:D , forRow row : Int )
{
fieldofActivitiesCollectionView.delegate = dataSourceDelegate
fieldofActivitiesCollectionView.dataSource = dataSourceDelegate
fieldofActivitiesCollectionView.reloadData()
}
}
and here is the second tableview cell class:
#IBOutlet weak var extra_infoCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
if let flowLayout = extra_infoCollectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize.init(width: 1.0, height: 1.0) }
}
}
extension extera_infoCell {
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDelegate & UICollectionViewDataSource>
(_ dataSourceDelegate:D , forRow row : Int )
{
extra_infoCollectionView.delegate = dataSourceDelegate
extra_infoCollectionView.dataSource = dataSourceDelegate
extra_infoCollectionView.reloadData()
}
}
First step: using Tags - you just need to use tag for them and use if else to choose which collection view has selected with tag so the answer is this :
if collectionView.tag == 1 {
do some thing//////
}else {
do some thing else}
and you should use this in both cellForRowAtIndexPath and numberOfRows methods you can use this for table view too
Second step: you have to change the name of 'collection view' that you are dequeueing inside the cellForRowAt method in CollectionView data source:
if collectionView.tag == 1 {
let cell = yourFirstCollectionView.dequeueReusableCell(...) as yourCell
....
return cell
} else {
let cell = yourSecondCollectionView.dequeueReusableCell(...) as yourCell
....
return cell
}
According to your error your reuse identifier doesn't match any cell in your storyboard. Click on your extera_info collectionView cell in interface builder. Select the attributes inspector tab. Under reuse identifier make sure you put in extera_infoCollectionViewCell
If you take the other tableview cell In different class , with NSObject features of storyboard it can help you , And it is easy to maintain .
Saeed's tag option above is likely the simplest answer, but found his description a little short so adding a more complete answer below for those who've never used tags before...
If abiding by MVC and placing collectionView dataSource methods inside the UITableView class (instead of inside the UITableViewCell classes), and wanting to avoid this " error:
Each Collection View you use will need its own dequeueReusableCell identifier:
In interface-builder, name all your identifiers for your collection view cells. CatPicCell & DogPicCell for instance.
In your CellForItemAt collectionView method, set up if-statements or switch statement such that each reuse identifier is set equal to the identifiers you created in interface-builder (step 1). If using switch/case, your value can be set to collectionView.tag. Tags can be numbered to identify each different collectionView. The tags are like turning your set of collectionViews into a dictionary or array, such that each collectionView gets its own unique key/index.
Go back into interface-builder, and go into your storyboard and select each collection view (each of which should be inside its own tableView cell). In Xcode's "attribute inspector" scroll down to the "View" section and 3 spaces down (Xcode 11, Swift 5) you'll see a field called "Tag". Assign an integer value to that collection view, and then repeat this process for each collection view which is going to be embedded in your UITableView cells.
Once you have all the collection views tagged with unique integers, you simply set your cases to the integers, and give each dequeueReusableCell identifier the same integer index as you provided in the storyboard.
Now when you tableView cell calls on the collectionView you've outletted in the TableViewCell classes, it will be able to acquire the proper dequeueReusable ID. You can put your data inside each switch case.
Voila, you now have ONE collectionView datasource set of required methods, but serving ALL of your collection views. EVEN BETTER, when someone expands the project and adds another collectionView it will be as easy as adding another case to the switch and identifier in the storyboard.
Example code could look like this:
// I need a switch statement which will set the correct (of the 3 collectionViews) dequeueReusable IDENTIFIER for the collectionView
switch collectionView.tag {
//if tableView is doing cell == 1, then "CatsCell"
//if ... cell == 3, then "DogsCell"
//if ... cell == 5, then "BirdsCell"
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CatsCell", for: indexPath) as! CatsCVCell
// put your required data here
return cell
case 3:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DogCell", for: indexPath) as! DogsCVCell
// example data
let dogs = dogController.fetch()
cell.name = dogs[indexPath.item].dogName
if let image = UIImage(data: groups[indexPath.item].image!) {
cell.image = image
}
return cell
case 5:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BirdCell", for: indexPath) as! BirdCVCell
// put data code here for birds collection view cells
return cell
default:
return UICollectionViewCell() // or write a fatalError()
}
note: you have two options for your default to the switch statement...
1. like above, a generic but empty cell instance
2. throw an error. The error should never throw bc you'll have all the cases, but an error could occur if someone else improves your code and add another collectionView but forgets to to add the switch case-- so make your error statement explain what's wrong precisely.
I'm working on a project in which i have a UICollectionView in UITableViewCell. My View hierarchy is like below:
TableViewCell
CollectionView
CollectionView Cell
Button(for which i want to access the know the tagValue).
I have two table view cell and in each tableView cell there is a UICollectionView And each UICollectionView contain 4 UICollectionViewCell, So i want to know the exact tagValue of my button contained within a UICollectionViewCell whenever it is pressed.
I have also given the tag value to button in cellForItem atIndexpath TableView cell class like so
cell.button.tag = indexpath.item
and i have also given selector to the button.
Try this :
Remove this line :
cell.button.tag = indexpath.item
Add this fuction
#IBAction func clickCollectionCellButton(sender : UIButton){
var cell: UICollectionViewCell? = (sender.superview?.superview as? UICollectionViewCell) //track your view hierarchy
var indexpath: IndexPath? = collectionView?.indexPath(for: cell!)
// do your additional work
}
No need to assign tag value you can directly get cell indexPath in your button exist
suppose you assign a selector to your button like this:-
#IBAction function buttonAction(_ sender:UIBUtton){
if let indexPath = viewIndexPathInCollectionView(sender , collView:pass your collectionView here){
// here you will get indexPath of your collectionView Cell
print(indexPath.item)
}
}
use this function to get index of collection view cell by passing cell item in this function
func viewIndexPathInCollectionView(_ cellItem: UIView, collView: UICollectionView) -> IndexPath? {
let pointInTable: CGPoint = cellItem.convert(cellItem.bounds.origin, to: collView)
if let cellIndexPath = collView.indexPathForItem(at: pointInTable){
return cellIndexPath
}
return nil
}
Hi in my application i want to add particular view in cell.contentview now situation is that where i can add this code. reason is if i am puting this code on cellforitematindexpath it will be added in last cell(indxpath.row) reason is in cellforrowatindexpath last cell will be loaded last so view will be added in last cell. while i want it on that cell which was displaying at current time
Here is my code. simply have to add subview
let cell : pdfImageCell = collectionView.dequeueReusableCell(withReuseIdentifier: "pdfImageCell", for: indexPath) as! pdfImageCell
cell.contentView.addSubview(userResizableView1)
Get the visible cell index like below in UICollectionView. You can also get multiple cells visible if they are visible on screen so selected the cell you want.
var visibleCurrentCell: IndexPath? {
for cell in self.collectionView.visibleCells {
let indexPath = self.collectionView.indexPath(for: cell)
return indexPath
}
return nil
}
Then get the cell from your index and add the subview in that cell.
You can add UIView in awakeFromNib() method. It will get the call only once.
class pdfImageCell : UICollectionViewCell {
//you can add here
override func awakeFromNib() {
var userResizableView1 = UIView()
self.contentView.addSubview(userResizableView1)
}
}
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
Here it is a method to reload the specific indexPaths in your collectionView
you can update your datasource on click and your cellForItemAtIndex will know from your data source that it has to have the subview this time
if (datasourcearray[indexpPath.row].isSubViewToBeAdded)
{
self.contentview.addsubview(subViewToBeAdded)
}