I have an application where I used table view with custom cells which works fine. The problem I am having now is I want to make it dynamic by this, the number of cell is not fixed. That is, the cell could be 1, 2, 3 or 5, depending on the count of an array. This cell UI varies, too
Cell1: could have an image and a label.
Cell2: could have two labels.
Cell3: could have a dayPicker.
this is the way to create the cells when I know the number returned
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "firstTableCell") as! FirstTableCell
// Set up cell.label
return cell
} else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "secondTableCell") as! SecondTableCell
// Set up cell.button
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "thirdTableCell") as! ThirdTableCell
// Set up cell.textField
return cell
}
}
but now numberOfRowsInSection varies and so those the view items.
how can I do this? Programmatically or otherwise, programmatically preferred.
A dynamic table view can be accomplished with an appropriate data model.
For example use an enum for the kind and a struct with a member to indicate the kind
enum CellKind {
case image, label, picker
}
struct Model {
let kind : CellKind
// other struct members
}
How many cells are displayed depends on the items in the data source array
var items = [Model]()
In numberOfRows return the number of items
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
In cellForRow display the cells depending on the kind rather than on the index path
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
switch item.kind {
case .image:
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell") as! ImageCell
// Set up cell.image
return cell
case .label:
let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell") as! LabelCell
// Set up cell.label
return cell
case .picker:
let cell = tableView.dequeueReusableCell(withIdentifier: "pickerCell") as! PickerCell
// Set up cell.picker
return cell
}
}
Related
I take tableView and two different cell.xib files , I want to display when i click cell1 then i should display cell2 data.
class TableView: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var array1 = ["Click1","Click2"]
var array2 = [[ "one","two","Three"],["Four","Five"]]
var selectedArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "MainCell", bundle: nil) , forCellReuseIdentifier: "MainCell")//This is used to add xib file with identifier
tableView.register(UINib(nibName: "SecondCell", bundle: nil) , forCellReuseIdentifier: "SecondCell")
}
//MARK:DataSource Methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "MainCell") as! MainCell
cell.textLabel?.text = array1[indexPath.row]
return cell
}
//MARK: tableViewDelegate Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "SecondCell") as! SecondCell
selectedArray = array2[indexPath.row]
cell.textLabel?.text = selectedArray[indexPath.row]
}
}
Tell how can i do that if i press first cell it should show 2nd cell values as per indexPath.row
I think you want to achieve expandable cells. You can use the header cell for this one.
You might want to read this:
Hope this helps!
For example, you can add a flag which indicates whether the first cell was tapped.
var wasFirstCellTapped = false
Then numberOfRowsInSection depends on this flag:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return wasFirstCellTapped ? array1.count : 1
}
In didSelectRowAt:indexPath set this flag to true. And perform tableView changes. The simplest way is to call tableView.reloadData(). But you can animate this using insertRowsAtIndexPaths
var allCellsArray = ["Click1","Click2"]
var displayingCellsArray = ["Click1"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return displayingCellsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if displayingCellsArray[indexPath.row] == "Click1" {
cell = self.tableView.dequeueReusableCell(withIdentifier: "MainCell") as! MainCell
}else {
cell = self.tableView.dequeueReusableCell(withIdentifier: "SecondCell") as! SecondCell
}
cell.textLabel?.text = displayingCellsArray[indexPath.row]
return cell
}
//MARK: tableViewDelegate Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = displayingCellsArray[indexPath.row]
//Below logic will show (Cell2 if Cell1 is clicked and hide Cell1)
// (and show Cell1 if clicked Cell2 and hide Cell2)
if data == "Click1" {
displayingCellsArray = [allCellsArray.last!]
}else {
displayingCellsArray = [allCellsArray.first!]
}
tableView.reloadData()
}
You should handle such logic in a separate datasource array by adding/removing that sort of data from this array which shows particular cells.
E.g
In your case you have two values in array Click1 & Click2
Click1 shows MainCell & Click2 shows SecondCell
So first add Click1 in your array and when this cell is tapped simply add Click2 in your array and reload. If you want to remove MainCell when SecondCell is displaying then while adding Click2 simply remove Click1
You can expand the second cell on click of first cell..
Refer this example to expand and collapse the cells
https://github.com/jonasman/JNExpandableTableView
I have two data sources, and two different classes for custom cells in my table.
I want by pressing one button to switch between sources and classes and update my UITableView accordingly.
Unfortunately It works only one time I switch from one set to another. It doesn't return back.
Hope my code will help to explain what I mean:
var displayMode : Int = 1
#objc func tappedButton(_ sender: UIButton?) {
if displayMode == 1 {
displayMode = 2
myTable.reloadData()
} else {
displayMode = 1
myTable.reloadData()
}
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if displayMode == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier(cellId,
forIndexPath: indexPath) as! Class1
cell.taskTitle.text = source1.text
return cell
}
else {
let cell = tableView.dequeueReusableCellWithIdentifier(cellId,
forIndexPath: indexPath) as! Class2
cell.taskTitle.text = source2.text
return cell
}
}
Should I delete table cells before changing mode?
You use the same cellID in
let cell = tableView.dequeueReusableCellWithIdentifier(cellId,
forIndexPath: indexPath) as! Class1
and
let cell = tableView.dequeueReusableCellWithIdentifier(cellId,
forIndexPath: indexPath) as! Class2
Should be two different cells for 2 different classes (2 different IDS)
1) You need to create 2 separate classes for cells:
class FirstCellClass: UITableViewCell {}
class SecondCellClass: UITableViewCell {}
2) Then register the cells(or add cells in Storyboard):
tableView.register(FirstCellClass.self, forCellReuseIdentifier: String(describing: FirstCellClass.self))
tableView.register(SecondCellClass.self, forCellReuseIdentifier: String(describing: SecondCellClass.self))
3) Check display mode and return specific cell cellForRowAtIndexPath and items count in numberOfRowsInSection:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch displayMode {
case .first:
return firstDataSource.count
case .second:
return secondDataSource.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch displayMode {
case .first:
let cell = tableView.dequeueReusableCell(
withIdentifier: String(describing: FirstCellClass.self),
for: indexPath
) as! FirstCellClass
configureFirstCell(cell)
return cell
case .second:
let cell = tableView.dequeueReusableCell(
withIdentifier: String(describing: SecondCellClass.self),
for: indexPath
) as! SecondCellClass
configureSecondCell(cell)
return cell
}
}
i have dynamic cells in tableView, but in top of dynamic cells i want add a static cell that contain two label.
i searched but i did not find my solution.
what should i do?
(i am begginer)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dictionary.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : TicketDetailTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TicketDetailTableViewCell
var dict = dictionary[indexPath.row]
cell.lblComment.text = dict["comment"] as? String
cell.lblDate.text = dict["date"] as? String
return cell
}
You have a couple options...
Use a .tableHeaderView - you can use any normal UIView + subviews, labels, images, etc, etc, etc
Create a second prototype cell, and use that cell as your "first row". You can lay it out in Storyboard however you want... just because it is a prototyped cell, doesn't mean you have to change anything when you use it.
Method 2 will end up looking similar to this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// if it's the first row, show the prototype cell with
// identifier "StaticCell"
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "StaticCell", for: indexPath)
return cell
}
// it's not the first row, so show the prototype cell with
// identifier "cell"
//
// Note: you will need to "offset" the array index since the
// "2nd row" is indexPath.row == 1
let cell : TicketDetailTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TicketDetailTableViewCell
var dict = dictionary[indexPath.row - 1]
cell.lblComment.text = dict["comment"] as? String
cell.lblDate.text = dict["date"] as? String
return cell
}
// you will also need to return +1 on the number of rows
// so you can "add" the first, static row
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dictionary.count + 1
}
The main idea is that you implement heightForRow data source method where depending on the cell index path you will return either a static height or automatic height. Something like this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == staticCellIndex {
return 80.0
}
return UITableViewAutomaticDimension
}
I am trying to implement a tableview with two sections. Each section has one type of cell which will be needed.
So section one cells are subclassed as PendingTVC
section two cells are subclassed as ScheduledCell.
I have the follow methods implemented but the cells are getting mixed up. For example if section one has 3 cells, the first 3 cells in section 2 have a mixed up label which correlates to section one's first 3 cells. Code below:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if arr != nil {
if section == 0 {
print("returning pending : \(pendingCount)")
return pendingCount
}
else{
print("returning scheduled count : \(scheduledCount)")
return scheduledCount
}
}
return 0
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Pending"
}
else{
return "Scheduled"
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50.0
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let data = arr![indexPath.row]
if indexPath.section == 0 {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Pending") as? PendingTVC{
PendingTVC.formattCell(cell: cell, data: data)
cell.selectionStyle = .none;
cell.delegate = self
return cell
}
}
else{
if let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledCell") as? ScheduledCell{
print("cellforrowat in scheduledCell")
ScheduledCell.formatCell(cell: cell, data: data)
cell.selectionStyle = .none;
return cell
}
}
return UITableViewCell()
}
You should fetch your data inside your section condition. If you fetch data beforehand its ambiguous with indexpath data you require.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.section == 0 {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Pending") as? PendingTVC{
let data = pendingarr![indexPath.row] //access pending data here
PendingTVC.formattCell(cell: cell, data: data)
cell.selectionStyle = .none;
cell.delegate = self
return cell
}
}
else{
if let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledCell") as? ScheduledCell{
print("cellforrowat in scheduledCell")
let data = scheduledarr![indexPath.row] // access scheduled arr here
ScheduledCell.formatCell(cell: cell, data: data)
cell.selectionStyle = .none;
return cell
}
}
}
You're using the same data array for for both sections.
let data = arr![indexPath.row]
Here you can see you are referencing indexPath.row as your index within your data array. This means the section is irrelevant. You have at least two options...
Create a 2d array in which the first index value is your section and the second is the row. e.g. dataArray = [[test, test2, test3], [otherTest, otherTest2, otherTest3]]. This can be accessed by changing your line to:
let data = arr![indexPath.section][indexPath.row]
or
2. Create two separate arrays... one for section 1 and one for section 2. Call them within your relevant checks for sections.
So i have created a table view with 2 custom cells.
i am trying to enter data into the 3rd cell, but im having a problem when returning how many cells & also to make my title array commence from my 3rd cell.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var row = indexPath.row
if(row == sliderIndex){
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Row
return cell
}else if (row == instagramIndex) {
var cell2 = tableView.dequeueReusableCell(withIdentifier: "instgramCell", for: indexPath) as! Insta
return cell2
} else {
var cell3 = tableView.dequeueReusableCell(withIdentifier: "blogCell", for: indexPath) as! blogCell
if (row == 3) {
cell3.blogTitle.text = titleArray[0]
}
return cell3
}
In last case, row is impossible to equal to 3. Change it to 2.
You return 3 in func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int). So value of row only would be [0, 1, 2]
And according to your code, you might make a mistake that set sliderIndex to 1 and set instagramIndex to 2. They should be 0 and 1.