Basically I need to do what is done here - Not able to add static UITableViewCell into tableview
But I need to do it in swift and I do not know how to convert the objective C solution into swift
Image of the solution I need in swift
First this is not the place to ask for code sample. You should have a look to solutions like Codementor where you can find exactly the kind of help you need ;)
They are different approaches to your problem.
First, you are using a xib, which makes it impossible to add cells in it.
Instead you couldd use a storyboard. In that case you'll be able to do whatever you want and add custom cell.
1 - If you really have to use xib, then you can load custom cells by using alanlo approach. I would suggest to use an extension to register your custom cell like so:
extension UITableViewCell {
class func register(tableView: UITableView) {
let identifier = String(describing: self)
let nib = UINib(nibName: identifier, bundle: nil)
tableView.register(nib, forCellReuseIdentifier: identifier)
}
}
Note that your cell identifier will need to have the same name as your custom tableviewcell class (here it would be YourCustomCellTableViewCell)
2- Then in viewdidload:
YourCustomCellTableViewCell.register(tableView: yourTableView)
3 - Then thanks to the fallowing extension you can easily dequeue your cell:
extension UITableView {
func dequeue<T: UITableViewCell>(indexPath: IndexPath) -> T {
let identifier = String(describing: T.self)
guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? T else {
fatalError()
}
return cell
}
}
4 - dequeue it in the cellForRowAtusing:
let cell: YourCustomTableViewCell = yourTableView.dequeue(indexPath: indexPath)
return cell
Try:
let nibName = "nameOfYourCellNib"
let cell = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)?.first as? {YOUR_CELL_CLASS_HERE}
May I know whether you are trying to create static cells or dynamic cells?
The solution can be simply changing the cell in the table view instead of adding it in codes or separate xib
Related
I have problems with my custom cell file in tableview. I managed to get it done using the out comment line shown below, but the performance was really bad when it had 10+ cells.
UsingdequeueReusableCell leads to this error:
'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier DiveNewsShort - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
which is strange, because I do register the nib in viewDidLoad(). I hope you can help me, I am getting frustrated by this.
class ProfilTableView: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "DiveNewsShort", bundle: nil), forCellReuseIdentifier: "DiveNewsShort")
tableView.register(DiveNewsShort.self, forCellReuseIdentifier: "DiveNewsShort")
}
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = Bundle.main.loadNibNamed("DiveNewsShort", owner: self, options: nil)?.first as! DiveNewsShort
// This one works as expected
let cell = tableView.dequeueReusableCell(withIdentifier: "DiveNewsShort", for: indexPath) as! DiveNewsShort
// This one does not
return cell }
Update:
I managed to get rid of the error by adding the register function in the cellForRowAt function, but I don't think that this is a efficient way actually. It should work within the vieDidLoad shouldn't it?
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(UINib(nibName: "DiveNewsShort", bundle: nil), forCellReuseIdentifier: "DiveNewsShort")
let cell = tableView.dequeueReusableCell(withIdentifier: "DiveNewsShort", for: indexPath) as! DiveNewsShort
return cell }
You don't need this line:
tableView.register(DiveNewsShort.self, forCellReuseIdentifier: "DiveNewsShort")
You already have registered the nib file one line before.
There are three ways to register cells for reuse/dequeuing:
You are programmatically creating the cells, in which case you register the class in viewDidLoad.
You are using a NIB, in which case you register the NIB in viewDidLoad.
You are using storyboard cell prototypes, in which case you don't have to register anything. The storyboard does all of this for you.
Since you are using NIBs, you should remove the registering of the class and only register the NIB. And you should do this in viewDidLoad. This process is outlined in https://stackoverflow.com/a/28490468/1271826 as well as in Reinier's answer.
Looking at your MCVE, your problem was a result of a more fundamental mistake, where you had a UIViewController trying to use another view controller, which was a UITableViewController, to manage the table. But UITableViewController has its own UITableView and won't use the one that you have an #IBOutlet for, so you were registering the NIB for a table view you weren't seeing. There were a ton of other issues here (e.g. if you really want a view controller within a view controller, you have to do view controller containment calls, etc.), but the simplest solution was to excise this separate UITableViewController from the project and when this was fixed, it works precisely as we described. See https://github.com/robertmryan/Divers for a working version of your MCVE.
You also didn't hook up the outlets for the other two controls in your cell (the switch and slider). Thus, if you changed either of those two controls and then scrolled, the cells are reused and you see the changed UIKit control that was done for some other cell, but was subsequently reused. To fix that, your custom UITableViewCell subclass should have outlets for all controls, and cellForRowAt must set values for all of these outlets. You also need some mechanism for the cell to inform the view controller when the switch and slider have changed and update the model accordingly, so when cellForRowAt was later called for that row, it would know the state of that CellData to set the control appropriately. A common solution for this is to use the protocol-delegate pattern. See the above GitHub repo, which illustrates this pattern, too.
I have build this protocol for help me in this process
protocol CBNibInstanceableCellProtocol {
static func getCellXib() -> UINib?
static func getReuseIdentifier() ->String
}
and in your class you have to implement those methods like here
//example implementation
extension CBUsersAttendanceEmptyCell : CBNibInstanceableCellProtocol
{
static func getCellXib() -> UINib?
{
if Bundle.main.path(forResource: "CBUsersAttendanceEmptyCell", ofType: "nib") != nil
{
return UINib(nibName: "CBUsersAttendanceEmptyCell", bundle: nil)
}
return nil
}
static func getReuseIdentifier() ->String
{
return "CBUsersAttendanceEmptyCell"
}
}
then in your viewDidLoad you must do something like this
//example code
self.collectionView.register(CBUsersAttendanceAvatarCell.getCellXib(), forCellWithReuseIdentifier: CBUsersAttendanceAvatarCell.getReuseIdentifier())
self.collectionView.register(CBUsersAttendanceCountCell.getCellXib(), forCellWithReuseIdentifier: CBUsersAttendanceCountCell.getReuseIdentifier())
self.collectionView.register(CBUsersAttendanceEmptyCell.getCellXib(), forCellWithReuseIdentifier: CBUsersAttendanceEmptyCell.getReuseIdentifier())
in your cellForRow
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CBUsersAttendanceCountCell.getReuseIdentifier(), for: indexPath) as? CBUsersAttendanceCountCell
{
return cell
}
You must have defined the class for you view in your xib, this is very important check this pictures
Hope this helps
I got stuck with simple issue and can't find any solution.
MyCell.xib has fileowner MyCell : UITableViewCell class.
I use it like that:
viewDidLoad method:
let nib = UINib(nibName: "MyCell", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier: "myIdentifier")
tableView cellForRowAt method:
let cell = tableView.dequeueReusableCell(withIdentifier: "myIdentifier", for: indexPath) as? MyCell
It works good.
I subclass my class to add some new methods:
class SuperCell : MyCell {
func coolMethod {
print("cool")
}
}
And try to use it like that:
let cell = tableView.dequeueReusableCell(withIdentifier: "myIdentifier", for: indexPath) as? SuperCell
it returns nil
How can I make it work?
I tried to create prototype cell in InterfaceBuilder with identifier myIdentifier and with class SuperCell, but it didn't help.
Why do I need it
I just want to use the same view (xib) for different cell classes.
In my case I have common cell (MyCell) with view (xib). MyCell completely describes fields (IBOutlets). Also I want to create some another cell classes that subclasses MyCell, but they will provide some behaviour of these IBOutlets. For example FirstMyCell : MyCell will have method setFieldsFrom(objectOne: ObjectOne) and SecondMyCell : MyCell will have another method setFieldsFrom(anotherObject: ObjectAnother).
Of course, I can just add this two methods into my MyCell class, but it will be unclean.
Do not set the files owner (remove it)
Make sure your your XIBs Custom Class is set to SuperCell:
In my App I use UITableView with custom cells.
for each cell I implement function to create it, and call these functions in cellForRow.
this is an code example from project :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == CellsTypes.profileImageCell.rawValue {
return createIntroCell()
}
else if indexPath.row == CellsTypes.fullNameCell.rawValue {
return createUserNameCell()
}
else if indexPath.row == CellsTypes.emailCell.rawValue {
return createEmailCell()
}
else {
return createPasswordCell()
}}
and these are the creation functions :
func createUserNameCell() -> TextFieldTableViewCell {
let fullNameCell = self.tableView.dequeueReusableCellWithIdentifier("text-field-cell") as! TextFieldTableViewCell
fullNameCell.awamirTextFieldNib.setNormalTextFieldCellContent("Full Name")
fullNameCell.awamirTextFieldNib.textFieldContent.tag = CellsTypes.fullNameCell.rawValue
fullNameCell.awamirTextFieldNib.textFieldContent.returnKeyType = .Next
fullNameCell.awamirTextFieldNib.textFieldContent.delegate = self
return fullNameCell
}
func createEmailCell() -> TextFieldTableViewCell {
let emailCell = self.tableView.dequeueReusableCellWithIdentifier("text-field-cell") as! TextFieldTableViewCell
emailCell.awamirTextFieldNib.setNormalTextFieldCellContent("Email")
emailCell.awamirTextFieldNib.textFieldContent.tag = CellsTypes.emailCell.rawValue
emailCell.awamirTextFieldNib.textFieldContent.returnKeyType = .Next
emailCell.awamirTextFieldNib.textFieldContent.delegate = self
return emailCell
}
func createPasswordCell() -> TextFieldTableViewCell {
let textFieldCell = self.tableView.dequeueReusableCellWithIdentifier("text-field-cell") as! TextFieldTableViewCell
textFieldCell.awamirTextFieldNib.setPasswordCellContent("Password")
textFieldCell.awamirTextFieldNib.textFieldContent.tag = CellsTypes.passwordCell.rawValue
textFieldCell.awamirTextFieldNib.textFieldContent.returnKeyType = .Next
textFieldCell.awamirTextFieldNib.textFieldContent.delegate = self
return textFieldCell
}
the problem is if I reload the tableview the content of cells changed because of the reusablity of the cells. i.e: after reload the tableview the content of the first cell become in the second cell, and the content of the sencond on became in the first cell!!
how can I prevent tableview from reusable the cells?!
Thanks.
Try using a different identifier for each of the cell types, so dont use "text-field-cell" for each one, make one "full name", "password" etc. not sure how you are going about creating your cells, but if you are using the registerNib or registerClass, you will need to register it for each different identifier
self.tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "full name")
self.tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "password")
self.tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "email")
Try to implement prepareForReuse method in your custom cell and set all fields text to nil.
override func prepareForReuse() -> Void {
awamirTextFieldNib.text = nil
}
Hope this help
Don't use the default UITableView and fight the reuse of cells, it's really embedded in it's behaviour.
Try to adapt your code so it works well with reusing cells, or if that's really impossible, you'd have to write your own custom table view I guess (but I don't recommend that at all)
You shouldn't prevent that, but if you really want to, I think that not setting cell identifier to your UITableViewCell should do the trick. When you'll call dequeueReusableCellWithIdentifier, it will find no cell and will create a new one. But again, it's not recommended as UITableView is made to be used with reuse ability.
Moreover, as #Fonix suggests, using a different cell identifier for each cell of your UITableView might solve your issue while keeping using cell reuse system.
Edit :
As you're talking about losing text content in your UITextField, maybe the single change to make is changing your #propery weak attribute to strong.
Hope this helps.
I try to create some universal extensions for UITableView and UICollectionView to reduce boiler code needed to work with collections.
For UITableView I have this:
extension UITableView {
public func dequeueReusableCell<T:UITableViewCell>(type: T.Type) -> T {
let tableCell : T
let cellIdentifier = String(T)
if let cell = self.dequeueReusableCellWithIdentifier(cellIdentifier) as? T {
tableCell = cell
} else if let _ = NSBundle(forClass: T.classForCoder()).pathForResource(cellIdentifier, ofType:"nib") {
self.registerNib(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier)
if let cell = NSBundle(forClass: T.classForCoder()).loadNibNamed(cellIdentifier, owner: nil, options: nil)[0] as? T {
tableCell = cell
} else {
//if anyone had better suggestion for fallback, you're welcome to comment
tableCell = T(style: .Default, reuseIdentifier: cellIdentifier)
}
} else {
tableCell = T(style: .Default, reuseIdentifier: cellIdentifier)
}
return tableCell
}
}
For UICollectionView currently I only have this:
extension UICollectionView {
public func dequeueReusableCell<T:UICollectionViewCell>(type: T.Type, indexPath: NSIndexPath) -> T {
let collectionCell : T
let cellIdentifier = String(T)
if let _ = NSBundle(forClass: T.classForCoder()).pathForResource(cellIdentifier, ofType:"nib") {
self.registerNib(UINib(nibName: cellIdentifier, bundle: nil), forCellWithReuseIdentifier: cellIdentifier)
collectionCell = self.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! T
}
else {
collectionCell = T()
}
return collectionCell
}
}
Which works but I don't think it's neat enough. What I would like to improve is to call registerNib only once. Unfortunately, I can't call dequeueReusableCellWithReuseIdentifier because it will fail on internal assertion. I've tried to play with try catch mechanism in Swift but to no avail.
Any suggestion?
I know I can just register nib once in VC, but that's not a point of having these categories.
Here's a sample code that you can use. The idea is to pass an array of alreadyRegistered nib names which is populated whenever you register your xib. You can use this in your code and from where you call you just have to pass an array. This could be a global array.
You just have to call this at the beginning of your function passing the name of the cell Id and the global array.
extension ViewController
{
func registerNibName(name:String, inout alreadyRegistered:[String])
{
if !alreadyRegistered.contains(name) {
alreadyRegistered.append(name)
//self.registerNib(name)
print("registerNib with name \(name)")
}
}
}
So you need a way to associate information about nib registration state with a NIB with certain name. Unfortunately, you create UINib instance again each time, so this flag can't be associated with UINib instance. For example, add a static dictionary with a mapping of identifiers to flags to the extension of UINib (as a property via associated objects, for instance). Or create UINib subclass, it would be even better. Set this flag first time you register a NIB. Check this flag next times.
EDIT: there is a great talk AdvancedCollectionView: Advanced User Interfaces Using Collection View presented on WWDC 2014. Source code is available here. There is a class AAPLShadowRegistrar which does exactly what you need: maintains a registry for reusable views and NIBs.
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.