I have a tableview with two custom nib cells registered. The cells and their corresponding connected classes are completely independent of each other.
The tableview based on a condition should present either of one of the two cells registered:
The tableview code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Show only the table cells that have content.
tableView.tableFooterView = UIView(frame: CGRect.zero)
let rowData = FriendsTab.potentialFriendList[indexPath.row]
if (rowData.pendingInvite){
let cell = potentialFriendTableView.dequeueReusableCell(
withIdentifier: requestCellIdentifier, for: indexPath)
as! FriendRequestCell
cell.activityIndicator.isHidden = true
cell.activityIndicator.hidesWhenStopped = true
cell.userName.text = firstName + " " + lastName
return cell
}
else{
let cell = potentialFriendTableView.dequeueReusableCell(
withIdentifier: potentialCellIdentifier, for: indexPath)
as! PotentialFriendCell
cell.activityIndicator.isHidden = true
cell.activityIndicator.hidesWhenStopped = true
cell.userName.text = firstName + " " + lastName
return cell
}
}
The cell registration code (from viewDidLoad):
let potentialFriendXib = UINib(nibName: self.potentialCellIdentifier, bundle: nil)
let requestFriendXib = UINib(nibName: self.requestCellIdentifier, bundle: nil)
self.potentialFriendTableView.register(potentialFriendXib,forCellReuseIdentifier: self.potentialCellIdentifier)
self.potentialFriendTableView.register(requestFriendXib,forCellReuseIdentifier: self.requestCellIdentifier)
For some reason the FriendRequestCell calls awakeFromNib() twice however the PotentialFriendCell works as expected (one single call).
I have searched the few (very old) SO questions on this but they seem to deal with nested (parent child) nibs which this project does not use (they are individual).
Any ideas where I've gone wrong?
All of this is done in InterfaceBuilder. In interface builder, open up your storyboard or xib that contains the tableviewcontroller. Then select to use Content: Dynamic Prototypes, the select 2 for Prototype Cells. You will then be given 2 TableView Cells. The next step is to do exactly what you did for the cells in each xib. Now there is no loading of nib, and no registering. You still have to do the dequeueReusableCell for each cell.
Related
I am trying to achieve a layout like this
My tableview has 3 cells (SubItemsCell (main cell), SubItemsListCell(n number of products inside main cell) and noSubstituteCell (fixed cell after the second cell count 1))
SubItemsCell has a "SELECT" Button that will expand and show SubItemsListCell, this cell will load as (dynamic) any count of products under the main SubItems Cell.
NoSubstitute Cell comes after the n number of products loads.
What my expected result is first load main cell that is self.archivedProducts and when u click each archived product's select button it expand and load self.newproducts and nosubstitute static cell. So main cell paired up with those 2 cells and show at once when we expand only.
So far I just loaded Main Cell only. I have no idea what should I do next, please give me an idea with a code or very similar example.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SubItemsCell", for: indexPath) as? SubItemsCell else {
fatalError ("SubstituteItems Cell not found!")
}
let product = self.archivedProducts[indexPath.row]
cell.titleLabel.text = product.title ?? ""
cell.priceLabel.text = "AED \(String(format: "%.2f", product.price ?? 0.00))"
cell.scaleLabel.text = product.uom ?? ""
cell.unavailableLabel.isHidden = false
cell.selectBtn.isHidden = false
let imageUrl = product.image?.url
let escapedUrl = imageUrl?.replacingOccurrences(of: "\\", with: "")
let replacedUrl = "\(escapedUrl ?? "")"
let url = URL(string: replacedUrl)
let plImage = UIImage(named: "whiteBg")
DispatchQueue.main.async {
cell.thumbImage.kf.setImage(with: url, placeholder: plImage, options: [.transition(.fade(0.2))])
}
}
I don't think you need a nested tableview. You can achieve the above with other elements. In order to achieve the layout above, my suggestion is the following:
Subclass your UITableViewCell into at least 2 different formats. There's a lot of ways to do this, my suggestion is to use a CocoaTouch Class w/ XIB (tutorial here) so you can be meticulous with the layout and constraints:
a. CellWDropdown (Cell #1 shown): You have a cell prototype that will include the image, the 3 labels in question and a button which act as the dropdown and will invoke a UIPickerView.
b. CellWStepper (Cells #2 and #3) : You'll have your image and 2 labels and what's called UIStepper for the +/- buttons
c. LastCell (Cell #4 if that's a cell? It could also be a view): likewise create the needed UI elements and constrain them appropriately.
Register the nib file you created:
tableView.register(UINib(nibName: "CellWDropDown", bundle: nil), forCellReuseIdentifier: "cellWDropdownIdentifier")
In cellForRowAt method, you invoke the XIBs created above to make the cell views based on the index OR desired cell type for example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellWDropdownIdentifier, for: indexPath) as! CellWDropdown
}
else if(indexPath.row == 1 || indexPath.row == 2){
let cell = tableView.dequeueReusableCell(withIdentifier: cellWStepperIdentifier, for: indexPath) as! CellWStepper
}
//do other actions/styling
}
Hook up delegate relationships/methods between the nibs you created and the UITableViewController. This is going to application specific.
There's a lot of detail that will have to fill in the gaps but this should put a major dent in getting started
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.
In my app I am using a custom tableViewCell using a xib file. I create the .xib file outlet the labels etc to my TableViewCell class
In my ViewController the code I used to populate and show the cell on the table view is as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let transaction = statementArray[indexPath.row]
let cell = Bundle.main.loadNibNamed("StatementCell", owner: self, options: nil)?.first as! StatementCell
cell.transAmount.text = String(transaction.transAmount)
cell.transDesc.text = transaction.transDesc
cell.transFees.text = String(transaction.transFees)
return cell
}
I am aware that the way tableViews work is that they reuse the cell that goes off the screen. Is the way i am loading the .xib and populating the cell correct? Or do I have to add something to my code?
First you need to register your custom cell with UITableView
yourTableView.register(UINib(nibName: "StatementCell", bundle: nil), forCellReuseIdentifier: "cellIdentifier")
then in UITableView Delegate method cellForRowAt you need to write
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) as! StatementCell
cell.textLabel?.text = "Sample Project"
return cell
now you can access your UITableViewCell properties with cell.propertyName
and you must take care that "cellIdentifier" and "forCellReuseIdentifier" both value must be same.
One more thing is you need to register your class for reuse identifier in viewDidLoad method.
YourTable.registerclass(tablecell, forCellReuseIdentifier: "identifier"
And make sure you have connect all the required outlets.
FIrst Register Custom cell nibCalss in tableview
let str1 : NSString = "StatementCell"
tableView.register(UINib(nibName: "StatementCell", bundle: nil), forCellReuseIdentifier: str1 as String)
Now initialise tableviewcell
let cell1:StatementCell = tableView.dequeueReusableCell(withIdentifier: str1 as String) as! StatementCell!
Now access tableviewcell outlet collection.
I have a collection view. On one of the collection cells, I have a tableview with 5 different custom cells. I am reusing each cell of the table view.
When the table is reloaded, I see new rows added just above the previously loaded cells. This appears like a new table has been added just above the previous table. I got this visual when I see the 3D view of the collection view:
Blue color is one of the 5 cells. Each time the table loads, it has added 5 sets of rows above previously loaded cells. What is happening here and how could this be possible?
override func viewDidLoad() {
super.viewDidLoad()
self.cabinTableView.registerNib(UINib(nibName:"CabinNetNib", bundle:
nil), forCellReuseIdentifier: cabinNetNib)
}
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellDetail = tableViewDataSourceArray[indexPath.row] as? cabinDetailsMO
let cellType = cellDetails?.cellTypeEnum
if cellType == CellType.Net {
let netLabelCell = self.questionsTableView
.dequeueReusableCellWithIdentifier(questionLabelCellIdentifier) as?
NetLabelTableViewCell
netLabelCell!.selectionStyle = UITableViewCellSelectionStyle.None
netLabelCell!.netLabel.attributedText = cellDetails?.attributedQuestionString
return netLabelCell!
}
else if cellType == CellType.Video {
let videoCell = self.cabinTableView.dequeueReusableCellWithIdentifier(cabinVideoCellIdentifier)
as! cabinVideoTableViewCell
videoCell.videoName = cellDetails?.questionVideo
videoCell.loadVideoItem(cellDetails?.questionVideo, videoFields: nil)
videoCell.selectionStyle = UITableViewCellSelectionStyle.None
shouldReloadAllCells = true
image_videoCellIndex = indexPath.row
return videoCell
}
else if (cellType == CellType.captian) {
let captianCell = self.cabinTableView .dequeueReusableCellWithIdentifier(captianCellIdentifier) as!
CaptianTableViewCell
captianCell .selectionStyle = UITableViewCellSelectionStyle.None
captianCell.question_idLabel.text = cellDetails?.contentidName
return aptianCell
}
}
First cell is alone a nib.
I have a UITableViewController in which I use more than one custom cell. I have used multiple custom cells in the past with no problem, and I am using the same strategy to implement them as I have in the past so this error baffles me. First, some code:
This is how I register the custom cells in viewDidLoad:
// Registering the custom cell
let nib1 = UINib(nibName: "OmniCell", bundle: nil)
tableView.registerNib(nib1, forCellReuseIdentifier: "omniCell")
let nib2 = UINib(nibName: "RankCell", bundle: nil)
tableView.registerNib(nib2, forCellReuseIdentifier: "rankCell")
This is how I create them in cellForRowAtIndexPath:
let cell1: OmniCell = self.tableView.dequeueReusableCellWithIdentifier("omniCell") as! OmniCell
let cell2: RankCell = self.tableView.dequeueReusableCellWithIdentifier("rankCell") as! RankCell
when I comment out the code related to the second custom cell the program runs fine, but when both custom cells are used I get this error:
this class is not key value coding-compliant for the key rankCell.
Both custom cells are implemented in an identical manner so I am not sure why the second custom cell generates this error while the first one does not. What am I missing?
Update: Upon request I am sharing the entire cellforrowatindexpath func:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// This line works:
let cell1: OmniCell = self.tableView.dequeueReusableCellWithIdentifier("omniCell") as! OmniCell
// It is this line that breaks if not commented out:
let cell2: RankCell = self.tableView.dequeueReusableCellWithIdentifier("rankCell") as! RankCell
// This line works:
let cell3: ProgressCell = self.tableView.dequeueReusableCellWithIdentifier("progressCell") as! ProgressCell
if indexPath.row == 0 {
return cell1
} else if indexPath.row == 1 {
return cell2
} else {
return cell3
}
}
I managed to resolve this. The problem was that there was an errant connection in the XIB. Refer to this picture:
There was an additional "broken" outlet above the one good one that was like:
rankCell ---> Rank Cell
But instead of a filled in circle it had what appeared to be an elongated symbol that was too small to make out. I removed the outlet and the app built and ran normally. I am not sure where that connection came from, none of the other custom cells had one like it.