How to use UITableViews to create static and dynamic rows with Swift - ios

This question follows up from this: Use UICollectionViews to create dynamic and multiple features.
I am able to create a static cell which displays the name and image of the recipe similar like this app:
Where I am stuck is creating a dynamic row which changes based on the amount of data inside i.e. utensils or nutritional values like the image below:
I know how to display rows of data on tableView normally. But not sure how to embed it into a section inside a tableView. I attempted to add multiple prototype cells and assign them to a subclass of UITableViewCell's. Then I try to use if statements in my cellForRow but this isn't soling my issue.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
//set the data here
cell.recipeTitle.text = recipe.name
cell.imageView.image = UIImage(named: "url")
return cell
}
else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as! SecondCell
//set the data here
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath) as! ThirdCell
//set the data here
return cell
}
}
I have also looked at this demonstration: https://medium.com/ios-os-x-development/ios-how-to-build-a-table-view-with-multiple-cell-types-2df91a206429, which is near to what I want to achieve but I have found it quite difficult to adapt to it.
If someone could direct me on how best to approach this or a good example then I would really appreciate it.

First you can't have static cells and dynamic cells in the same tableView. So how do you work around that? Define each of the static cells in the sections they belong in as well as the dynamic cells in the sections they belong to. That, however doesn't look like what you are trying to do. You just want multiple sections in the same tableView, each section with a different list of data
To do this you will need the number of sections so use the tableView(_:numberOfSections:) function.
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
You can then(and probably should) give each of those sections a title by initializing an array with the titles in your tableViewController(assuming thats what you are using. It could also just be a tableView).
let headerTitles = ["Nutritional Values", "Utensils", "Ingredients"]
Then use the tableView(_:titleForHeaderInSection:)
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < headerTitles.count {
return headerTitles[section]
}
return nil
}
Now you can start defining your rows by the sections.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if indexPath.section == 0 {
//Setup up the first row
if indexPath.row == 0 {
//I'm not sure where or how you defined First/SecondCell but this may not work depending on those two questions.
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
return cell
} else if indexPath.row == 1 {
let cell = Bundle.main.loadNibNamed("StaticCell", owner: self, options: nil)?.first as! StaticCell
return cell
}
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! SecondCell
//setup cell1 n your storyboard to have a label with a tag # of 12(or whatever number you want to use)
//you also want an array with your utensil data accessible here
let label = cell.viewWithTag(12) as! UILabel
label.text = utensilNames[indexPath.row]
return cell
} else if indexPath.section == 2 {
let cellIngredients = tableView.dequeueReusableCell(withIdentifier: "Ingredients", for: indexPath)
tableView.deselectRow(at: indexPath, animated: true)
return cellIngreidents
}
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
return cell
}
The point here is to use sections then rows to distribute your data.
Just to clarify Section 0 Row 0 - N would be where you're static rows are setup. I found it best to use XIB files subclassing TableViewCell.
Hope this helps.
EDIT So the way I'm looking at the "static" cells is in the first section the xib is the only put exactly where you tell it to be placed. In the example above the first section in the second cell is the

Related

How to implement a tableview cell that contains 2 more cells inside counts are not same

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

Add A UITableView Cell After Another One

Essentially I have a tableview with two types of cells. One is a cell that contains a number that is being pulled from an array. The second cell is just supposed to act as a spacing cell but can contain information. However, I've only seen solutions that have the second cell come after the first if it its row is equal to 0 or if it is sectioned off.
The array of numbers is [1,2,3,4,5,6,7] and the first cell type pulls from it to display each number
I'm trying to get something that looks like this:
celltypeone: 1
celltypetwo
celltypeone: 2
celltypetwo
celltypeone: 3
celltypetwo
celltypeone: 4
celltypetwo
... and so on.
I thought about trying to get the space cell to come after every next cell but I didn't think that made sense.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
if index >= 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "productsblock", for: indexPath ) as! DetailTBCell
return cell
}
else {
let spacecell = tableView.dequeueReusableCell(withIdentifier: "spacingcell", for: indexPath ) as! DetailSpaceTBCell
return spacecell
}
}
This prints all the numbers from the array and displays them but the second cell does not show at all.
I'm unsure of what to do or how to get it to work.
if (index % 2) == 0 | try if index number is double put the first tableCell else put the other one
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
if (index % 2) == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "productsblock", for: indexPath ) as! DetailTBCell
return cell
}
else {
let spacecell = tableView.dequeueReusableCell(withIdentifier: "spacingcell", for: indexPath ) as! DetailSpaceTBCell
return spacecell
}
If you notice your product cell needs to be shown at even position and the spacing cell at odd position.
`index >= 0 ` needs to replaced with `index % 2 == 0 `
Moreover in order to fetch data from product array you will be needing to divide the index by 2.
let products = [1,2,3,4,5,6,7]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
if index % 2 == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "productsblock", for: indexPath ) as! DetailTBCell
// Fetching data from array products defined above
let data = products[index / 2]
return cell
}
else {
let spacecell = tableView.dequeueReusableCell(withIdentifier: "spacingcell", for: indexPath ) as! DetailSpaceTBCell
return spacecell
}

Adding a cell on the top of a dynamic UITableViewCell

I add a cell with a label in it in a new section on the top of the tableView as section 0 and i show and hide this section according to what type of data i'm displaying.
It works fine when there is no data in the hashtag type posts then when there is hashtag data to be displayed in the array like two or three items it works fine and the top section 0 cell is displayed then when i scroll down and up again i get an error in the AppDelegate after trying to return the top section cell.
I know the question is a little bit complicated but what i'm trying to achieve is to display and hide a cell on the top of my feed according to the type of data i'm displaying in my tableview. If hashtag news feed data then show the top cell in section 0 if showing ordinary news feed in the tableview then return only one section and don't load the top section with the cell inside of it.
By the way i'm displaying the cell as a Nib. And declaring it in the viewDidLoad
let reloadNib = UINib(nibName: "ReloadTableViewCell", bundle: nil)
feedTableView.register(reloadNib, forCellReuseIdentifier: "reloadCell")
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102a772a0)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
switch indexPath.section {
case 0:
if hashPostsOnly {
let reloadCell = tableView.dequeueReusableCell(withIdentifier: "reloadCell", for: indexPath) as! ReloadTableViewCell
return reloadCell // ERROR AFTER RETURNING CELL
} else {
//For the protocol delegate i made
cell.delegate = self
cell.feed = feeds[indexPath.row]
cell.postCommentTextView.tag = indexPath.row
cell.cellIndexPath = indexPath
cell.userProfilePhotoBtn.tag = indexPath.row
cell.postMoreCommentsBtn.tag = indexPath.row
cell.postMoreCommentsBtn.addTarget(self, action: #selector(moreCommentsTapped(_:)), for: .touchUpInside)
return cell
}
case 1:
//For the protocol delegate i made
cell.delegate = self
cell.feed = feeds[indexPath.row]
cell.postCommentTextView.tag = indexPath.row
cell.cellIndexPath = indexPath
cell.userProfilePhotoBtn.tag = indexPath.row
cell.postMoreCommentsBtn.tag = indexPath.row
cell.postMoreCommentsBtn.addTarget(self, action: #selector(moreCommentsTapped(_:)), for: .touchUpInside)
return cell
default:
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
if hashPostsOnly {
return 2
} else {
return 1
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if hashPostsOnly {
return 1
} else {
return feeds.count
}
} else {
return feeds.count
}
}
Here is a screen shot of what i'm achieving but when i scroll down then up it reloads the top section cell "Reload Feeds" and then error.
Since there are just two sections and you had duplicate code, things can be simplified to:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.section == 0 && hashPostsOnly
{
let reloadCell = tableView.dequeueReusableCell(withIdentifier: "reloadCell", for: indexPath) as! ReloadTableViewCell
return reloadCell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
//For the protocol delegate i made
cell.delegate = self
cell.feed = feeds[indexPath.row]
cell.postCommentTextView.tag = indexPath.row
cell.cellIndexPath = indexPath
cell.userProfilePhotoBtn.tag = indexPath.row
cell.postMoreCommentsBtn.tag = indexPath.row
cell.postMoreCommentsBtn.addTarget(self, action: #selector(moreCommentsTapped(_:)), for: .touchUpInside)
return cell
}
}
I can't be 100% sure without knowing the exact error you're getting or knowing if there are other issues in the code elsewhere causing this, but:
As a general rule, dequeuing twice from a table view and returning a single cell does bad things in weird and mysterious ways. Refactor your code to only deuque a regular cell when you need it and not to do so when you're showing the refresh button

xcode8 swift3 multiple UITableViews with custom cells with

Following up to the excellent write up of a ViewController with a single tableView, I'd like to extend the question to having 2 separate tableViews and custom cells belonging to each one independently.
At the moment, I have the following skeleton, which is semi-working, and I am sure there is a more elegant and less naive approach to solving this.
after the viewDidLoad()
vInfoTV.dataSource = self
vInfoTV.delegate = self
vInfoTV.tag = Int.min
vAppTV.dataSource = self
vAppTV.delegate = self
vAppTV.tag = Int.max
numberOfRowsInSection function
func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
if tableView.tag == Int.min {
return mydata.cats.count
} else {
return mydata.dogs.count
}
}
Is it appropriate to set the tableView's tags as I do here, and switch on them based on tag value?
Cells in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView.tag == Int.min {
// Cat Table View
if indexPath.row == 0 {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "firstCustomCell")
//set the data here
return cell
}
else if indexPath.row == 1 {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "secondCustomCell")
//set the data here
return cell
}
else {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "thirdCustomCell")
//set the data here
return cell
}
} else {
// Dog Table View
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "poodleCell", for: indexPath) as! NewApplicationViewCell
cell.typeL.text = Dogs[(indexPath as NSIndexPath).row].type
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "mastifCell", for: indexPath) as! InitialDMVInspectionTableView
cell.typeL.text = Dogs[(indexPath as NSIndexPath).row].type
return cell
}
}
What is the best way to now hide a tableView, which has no cells (ie no data)?
Is this the right way to do this? All comments welcome!
THANK YOU!
Is it appropriate to set the tableView's tags as I do here, and switch on them based on tag value?
yes, you did exactly how I would do. Set the tag of your tables' differently. However I would not say Int.min or Int.max, rather I would want to know instantly what did I set as the tableviews' tag. So, I would just pick a number like 99 and 100. the reason I would not pick 0 and 1 is by default any object's tag is 0. So, if I put 99, I would be just keeping myself safe saying that even if someone comes and drag another table view inside my view, it will still not conflict with the ones before.
What is the best way to now hide a tableView, which has no cells (ie no data)?
Your tableview will not show up if you don't have data as in your numberOfRowsInSection, you set the row number to be the number of data in your desired data array.

Implementing two UITableViews with the same custom cell for reuse

I have currently have two UITableViews populated with contacts for the app. I have one for simply viewing them and editing/deleting and one for searching/picking contacts from a list. However, I'm getting a returned nil value when trying to use the same custom class cell for both UITableViews.
These are my two cellForRowAtIndexPath functions.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("SecondCell") as! ContactCell
let item = contacts[indexPath.row]
cell.meetupLabel?.text = item.fullName
return cell
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("FirstCell") as! ContactCell
let item = contacts[indexPath.row]
cell.label?.text = item.fullName
return cell
}
If the table did not have a cell named FirstCell or SecondCell, the dequeueReusableCellWithIdentifier(_:) method will return nil, and you will need to construct the cell yourself.
// no don't do this.
let cell: ContactCell
if let c = tableView.dequeueReusableCell(withIdentifier: "FirstCell") as? ContactCell {
cell = c
} else {
cell = ContactCell(style: .default, reuseIdentifier: "FirstCell")
}
You should use dequeueReusableCell(withIdentifier:for:), which was introduced in iOS 6, if you would like UIKit to construct the cell for you:
// swift 3
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell",
for: indexPath) as! ContactCell
// swift 2
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell",
forIndexPath: indexPath) as! ContactCell
...
Also, check if you have given the correct reuse-identifiers to the cells correctly in the interface builder.
As you said you are getting nil, my quick guess is that you haven't registered the cell at some point, runs earlier than this cell event. Look at this thread on how to register cell.

Resources