I have a few custom tableview cells which all have a different unib and different classes and want to reorder the cells and persistent safe their reordered position.
I have registered every cell and UINib in my TablevViewController
let test1Nib = UINib(nibName: "Test1", bundle: nil)
myTableView.register(overViewNib, forCellReuseIdentifier: "Test1")
let test2Nib = UINib(nibName: "Test2", bundle: nil)
myTableView.register(todoNib, forCellReuseIdentifier: "Test2")
let test3Nib = UINib(nibName: "Test3", bundle: nil)
myTableView.register(financeNib, forCellReuseIdentifier: "Test3")
let test4Nib = UINib(nibName: "Test4", bundle: nil)
myTableView.register(upcomingNib, forCellReuseIdentifier: "Test4")
I have then added the UINibs to my testArray:
testArray.append(test1Nib)
testArray.append(test2Nib)
testArray.append(test3Nib)
testArray.append(test4Nib)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = myTableView.dequeueReusableCell(withIdentifier: "Test1", for: indexPath)
return cell
} else if indexPath.row == 1 {
let cell = myTableView.dequeueReusableCell(withIdentifier: "Test2", for: indexPath)
return cell
} else if indexPath.row == 2 {
let cell = myTableView.dequeueReusableCell(withIdentifier: "Test3", for: indexPath)
return cell
} else {
let cell = myTableView.dequeueReusableCell(withIdentifier: "Test4", for: indexPath)
return cell
}
}
The moveRowAt: function is doing as expected
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = self.testArray[sourceIndexPath.row]
testArray.remove(at: sourceIndexPath.row)
testArray.insert(movedObject, at: destinationIndexPath.row)
NSLog("%#", "\(sourceIndexPath.row) -> \(destinationIndexPath.row)")
}
At this point I got a few issues
1. after reorder the cells are reloaded since I have fixed their index path to a static value and well now the questions is how can I prevent
them to reorder them selfs and 2. How can I persist this order
I hope I can find help here - I researched the web a lot now but nothing suites my needs.
Hope I can find a solution or some links or code snippets.
thanks a lot!
You should not force the check of indexPath row to decide which cell to load.
You should get their type and then switch into it. something like:
let type = testArray[indexPath.row]
switch type {
case Type1:
let cell = myTableView.dequeueReusableCell(withIdentifier: "Test1", for: indexPath)
return cell
case Type2:
let cell = myTableView.dequeueReusableCell(withIdentifier: "Test2", for: indexPath)
return cell
}
How you set this type depends on you. There are several ways to do this, pick your favorite.
Related
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
}
}
Number of row is 3. But I see more cell in tableView when scroll. I see cells with height 94 with separator color. However, I want only 3 cells in table view. Why do I see more cells than 3? Can anybody explain it?
Thank you
tableStyle.separatorColor = UIColor.red
tableStyle.allowsSelection = false
tableStyle.isScrollEnabled = true
// register UINib for LogoStyle1, FieldStyle1, ButtonStyle1
tableStyle.register(UINib(nibName: "LogoStyle1", bundle: nil), forCellReuseIdentifier: "LogoStyle1")
tableStyle.register(UINib(nibName: "FieldStyle1", bundle: nil), forCellReuseIdentifier: "FieldStyle1")
tableStyle.register(UINib(nibName: "ButtonStyle1", bundle: nil), forCellReuseIdentifier: "ButtonStyle1")
self.view .addSubview(tableStyle)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return 120
}else if indexPath.row == 1 {
return 272
}else{
return 94
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "LogoStyle1", for: indexPath) as! LogoStyle1
cell.backgroundColor = UIColor.clear
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "FieldStyle1", for: indexPath) as! FieldStyle1
cell.backgroundColor = UIColor.black
cell.delegate = self
return cell
case 2:
let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonStyle1", for: indexPath) as! ButtonStyle1
cell.backgroundColor = UIColor.clear
cell.delegate = self
return cell
default:
fatalError()
}
}
here is the screenshot:
These are not cells, but the tableFooterView, which is filling the available space, get rid of it as such:
tableStyle.tableFooterView = UIView()
I have some custom UITableViewCell,and would using some of them randomly. But after I register them ,it will crashed in tableView(_cellForRowAt:).
Here is my code:
in viewDidLoad method
tableView.register(CustomACell.self, forCellReuseIdentifier: "Identifier")
tableView.register(CustomACell.self, forCellReuseIdentifier: "Identifier")
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = dataSource[indexPath.row]
if let type = model.type {
switch type {
case .A:
let cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! CustomACell
cell.assgin(message: model)
return cell
case .B:
let cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! CustomBCell
cell.assgin(message: model)
return cell
}
}
let cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier) as! CustomACell
cell.assgin(message: model)
return cell
}
If I register both ,it will crashed at case .A. If I won't, some of them would crash at tableView.dequeueReusableCell.
Here is one of the console error info:
Could not cast value of type 'TM.CustomACell' (0x10bb40940) to 'TM.CustomBCell' (0x10bb40578).
Change the CellReuseIdentifiers. You use same for both custom cells. Use diffrent identifires for diffrent cells.
var nibName = UINib(nibName: "Identifier1", bundle: nil)
self.tableView.register(nibName, forCellReuseIdentifier: "Identifier")
nibName = UINib(nibName: "Identifier2", bundle: nil)
self.tableView.register(nibName, forCellReuseIdentifier: "Identifier2")
Then change the cellForRowAt, try following code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = dataSource[indexPath.row]
if let type = model.type {
switch type {
case .A:
let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier1") as! CustomACell
cell.assgin(message: model)
return cell
case .B:
let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier2") as! CustomBCell
cell.assgin(message: model)
return cell
default :
let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier1") as! CustomACell
cell.assgin(message: model)
return cell
}
}
}
if you created a separate Nib, then you can register else no need to register
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->UITableViewCell{
var cell = UITableViewCell(style: .default, reuseIdentifier: "pgIdentifier")
if (XXX == true) {
let pgtcell = tableView.dequeueReusableCell(withIdentifier: "indetifier", for: indexPath) as! CustomCell1
} else {
let pgtcell = tableView.dequeueReusableCell(withIdentifier: "indetifier", for: indexPath) as! MyCustomCell1
}
}
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifier", for: indexPath) as? MYVerticalCell else {
fatalError("Couldn't deque `Vertical Cell`")
}
cell.view.backgroundColor = .black
return cell
view outlet is connected with cell
Throws unexpectedly found nil while unwrapping an optional value
First create xib of custom cell with identifier,
Register cell before use, using below example,
tableView.register(UINib(nibName: "custCell", bundle: nil), forCellReuseIdentifier: "custCell")
And then use below in "func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell"
var cell = tableView.dequeueReusableCell(withIdentifier: "custCell") as? CustCell
if (cell == nil) {
cell = CustCell(style: UITableViewCellStyle.default, reuseIdentifier: "custCell"
}
I'm running into some problems today in my application. I'm using a UITableView to present different cells some contain a specific button, some display a game your currently in, some are just for spacing between cells.
However im running into some problems when I do tableView.reloadData().
When I click on 'new game' a new game cell should be added, but instead of that the last cells are moved down (like expected, because something comes in between), however the upper cells that should change, they don't. I expect this to be because of reusing (or "caching"). Maybe someone can help me out on how to fix this.
Here is an image of what is happening explanation
Now I have different cells, all with their own Identifiers for example: "Emptycell", "Gamecell", "startnewgameBtnCell". I do this because I create each cell in storybuilder.
here is my code for cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let identifier = cell_identifiers[indexPath.row][0] as! String
if ((cell_identifiers[indexPath.row][1] as! String) == "MYTURN" ) {
var cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! GameViewCell
let match = self.myTurnMatches[indexPath.row - 4]
setupGameViewCellObject(match)
}
if ((cell_identifiers[indexPath.row][1] as! String) == "NOT" ) {
var cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! FirstMenuTableViewCell
}
return cell
In this code I check if its a gamecell or not, emptycells or cells that contain a label or button are all FirstMenuTableViewCell. Only GameCell have their own class GameViewCell.
I have also double checked to see if my identifiers are build up correctly and they are.
Maybe someone can explain me exactly whats happening and what might be the correct approach to solve the situation I'm in, I think I may not fully understand how to use UITableViewCell with custom cells.
Thanks for reading
You create new variables called cell in the if statements. You are hiding the outer cell.
Use one cell variable.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell
let identifier = cell_identifiers[indexPath.row][0] as! String
if ((cell_identifiers[indexPath.row][1] as! String) == "MYTURN" ) {
cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! GameViewCell
let match = self.myTurnMatches[indexPath.row - 4]
setupGameViewCellObject(match)
}
if ((cell_identifiers[indexPath.row][1] as! String) == "NOT" ) {
cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! FirstMenuTableViewCell
}
return cell
}
Try update your code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let identifier = cell_identifiers[indexPath.row][0] as! String
if ((cell_identifiers[indexPath.row][1] as! String) == "MYTURN" ) {
var cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! GameViewCell
let match = self.myTurnMatches[indexPath.row - 4]
setupGameViewCellObject(match)
return cell
}
if ((cell_identifiers[indexPath.row][1] as! String) == "NOT" ) {
var cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! FirstMenuTableViewCell
return cell
}
return cell
if me, i coded:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier = cell_identifiers[indexPath.row][0] as! String
if ((cell_identifiers[indexPath.row][1] as! String) == "MYTURN" ) {
let cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! GameViewCell
let match = self.myTurnMatches[indexPath.row - 4]
setupGameViewCellObject(match)
return cell
}else { // if you sure just have 2 cell type
let cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! FirstMenuTableViewCell
return cell
}
}
But i think you should use section in tableview,it is better. So, you have 3 section, and 2 headerofsection in there. the first section have 1 row, the second have self.myTurnMatches.count row.... :
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 || section == 1{
return 44
}else{
return 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 1{
let cell = tableView.dequeueReusableCellWithIdentifier("GameViewCell 's identifier", forIndexPath: indexPath) as! GameViewCell
let match = self.myTurnMatches[indexPath.row - 4]
setupGameViewCellObject(match)
return cell
}else{
let cell = tableView.dequeueReusableCellWithIdentifier("FirstMenuTableViewCell 's identifier", forIndexPath: indexPath) as! FirstMenuTableViewCell
return cell
}
}