How do I create a custom UITableViewCell class in Swift? - ios

I'm trying to follow along the following Using Auto Layout in UITableView for dynamic cell layouts & variable row heights top answer in Swift but for the IOS 7 version to mitigate some bugs in the IOS 8.
Right now, my custom cell class is:
class PostCell: UITableViewCell {
#IBOutlet var name: UILabel!
#IBOutlet var timestamp: UILabel!
#IBOutlet var postBody: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
In my Storyboard I set my custom class as the class for the cell. I don't know if I have to use my reuseIdentifier anywhere. I don't know how to get that working in the next piece of code.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = PostCell()
var post = self.posts[indexPath.row]
cell.name.text = post.name
cell.timestamp.text = post.timestamp
cell.postBody.text = post.body
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(feed.bounds), CGRectGetHeight(cell.bounds))
cell.setNeedsLayout()
cell.layoutIfNeeded()
var height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
height += 1.0
return height
}
I thought that would work, but when I call cell.name.text = post.name, I get an EXC_BAD_INSTRUCTION. I'm guessing the cell isn't properly hooked up to the storyboard somehow or something. How do I fix this?

The problem is that you don't understand how nibs work. You have set up your PostCell to be configured when it is loaded from its nib:
class PostCell: UITableViewCell {
#IBOutlet var name: UILabel!
}
So that means that name is nil - until this cell is instantiated from the nib, at which time the outlet will be hooked up. But then later you say:
var cell = PostCell()
But that is the wrong PostCell! You didn't load it from the nib; you just made one out of whole cloth. So since there was no nib-loading, the outlet (as you rightly suspect) was naturally never connected, and so cell.name is nil.
So the solution is: do not make one out of whole cloth. Load it from the nib.

Related

Button Action from UITableViewCell to ViewController

I am trying to implement the Delegate way to do some action on a button tap from my UITableViewCell to a ViewController. The following is what I have so far -
TableViewCell:
protocol UserCellDelegate : class {
func disableUser(cell: UserCell, button: UIButton)
}
class UserCell : UITableViewCell {
#IBOutlet weak var userLabel: UILabel!
#IBOutlet weak var userDescription: UILabel!
#IBOutlet weak var writeOutUser: UIButton!
weak var delegate: UserCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
writeOutUser.layer.cornerRadius = 5.0
}
#IBAction func disableUserButtonAction(sender: UIButton) {
delegate?.disableUser(self, button: writeOutUser) //self here I believe to be the cell
}
}
ViewController:
class UserDetailTableViewController : UIViewController, UserCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let userCell = UserCell()
userCell.delegate = self
}
func disableUser(cell: UserCell, button: UIButton) {
print("VC: User Disabled")
}
}
The problem here is, the disableUser function in my ViewController never gets called. What am I doing wrong?
What is the best way to do it?
I had referred to the SO Approved Answer Here which is what I am following the same, but with no luck.
Any help will be appreciated. Thanks!!
You should set the delegate of your cell in the cellForRowAtIndexPath method.As #vadian said,In the viewDidLoad you should not initialize your cell using default initializer, and the delegate is nil for your cell by this way in viewDidLoad.
Either initialise your cell using the init(style: reuseIdentifier) or set the delegate in the cell for row method as you will initialize the cell in that method anyway.
When you're doing this:
let userCell = UserCell()
userCell.delegate = self
You are setting the delegate of userCell, and only the delegate of userCell. I'm sure that the table view cells displayed in your table view is not userCell.
So instead of setting the delegate of userCell, you should set the delegate of the cells that you actually want to show in the table view. The most probable place to create new cells that you want to show in table view is in the tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) method.
In the method, you must have something like this:
let cell = ...
// setting properties of cell using the model
return cell
You just need to add this line before the return:
cell.delegate = self
In your code UserCell acts like a UIView. You forgot adding userCell to current controller's view. If you want to use UITableViewCell, the better way is using it with UITableView.

Swift TableViewCell empty when cell has values

I have a custom cell with some simple labels and a UIImage. Everything appears to be set correctly, and stepping through the debugger shows that everything is getting a value and even using the print in the debugger shows that the labels have text. However my table view is still empty when executed. I have been looking at this for too long and cannot figure out the problem.
Here is the cell code
class CurrentFileCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var statusImage: UIImageView!
#IBOutlet weak var dateLabel: UILabel!
var currentContent: AircraftContent! {
didSet{
setStyles(Constants.appStyleSetting)
self.nameLabel.text = currentContent.contentName
self.dateLabel.text = currentContent.contentStatus
self.statusImage.image = UIImage(named: "color_label_circle_green")
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
private func setStyles(settings: StyleSettings) {
let tableFont = UIFont(name: settings.bodyFont, size: CGFloat(settings.tableFontSize))
nameLabel.font = tableFont
dateLabel.font = tableFont
// Text color
let tableFontColor = settings.tableFontColor
nameLabel.textColor = tableFontColor
dateLabel.textColor = tableFontColor
}
Here is the ViewController code with a tableview inside.
class CurrentFilesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var content: AircraftContent?
#IBOutlet weak var currentFiles: UILabel!
#IBOutlet weak var downloadingLabel: UILabel!
#IBOutlet weak var readyLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.content = loadContent()
setStyles(Constants.appStyleSetting)
//self.tableView.reloadData()
// Do any additional setup after loading the view.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CurrentFileCell", forIndexPath: indexPath) as? CurrentFileCell
cell?.currentContent = content
return cell!
}
func loadContent() -> AircraftContent {
return (NSKeyedUnarchiver.unarchiveObjectWithFile(AircraftContent.ArchiveURL.path!) as? AircraftContent)!
}
private func setStyles(settings: StyleSettings) {
let titleFont = UIFont(name: settings.bodyFont, size: CGFloat(settings.titleFontSize))
let key = UIFont(name: settings.bodyFont, size: CGFloat(settings.tableFontSize))
currentFiles.font = titleFont
downloadingLabel.font = key
readyLabel.font = key
// Text color
let titleFontColor = settings.titleFontColor
currentFiles.textColor = titleFontColor
downloadingLabel.textColor = titleFontColor
readyLabel.textColor = titleFontColor
}
Here are some images showing the debug location where the cell is not empty, and also printing out the label which has a value, but isn't being shown during simulation.
http://imgur.com/a/dBkpe
This is an image showing the prototype cell. The cell has the correct class set as well as the identifier.
http://imgur.com/PKtFTeQ
Lastly another image showing that the prototype cell is linked to the labels within the CurrentFileCell.
http://imgur.com/nW0QUjM
Any help at all with this would be appreciated. I have tried recreating everything but continue to be stumped as it seems like everything is how it should be.
You have to implement the 'heightForRowAtIndexPath' method for the table view
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height:CGFloat = 75
return height
}
You may consider registering the custom class as it does not appear that you did. You can do that by using the following code in the viewDidLoad of your View Controller.
tableView.registerClass(CurrentFileCell.self, forCellReuseIdentifier: "cell")
If you are using an external nib you will want to use registerNib instead like so:
tableView.registerNib(UINib(name:"ReplaceWithYourNibName", bundle: nil), forCellReuseIdentifier: "ReuseIdentifier")
Of course, replace ReplaceWithYourNibName and ReuseIdentifier with the appropriate values. In addition, if your nib is in a different bundle specify that instead of nil (nil defaults to the main bundle).
However, do not use both registerClass and registerNib as whichever one you call last will be used and they were designed to be mutually exclusive. Whenever you make a custom UITableViewCell you must use either of the two for it to work unless you have set it explicitly in the storyboard.
Also, you could instead, use prototype cells to define your custom cell, which would, I believe, automatically register the cell. But only if you did not use prototype cells make sure to use registerClass or registerNib.
Good luck! Hope this helps!
If your cell is a static cell, then you need to comment out these methods in UITableViewDataSource:
/* override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
} */
I had the same issue.
Data has values and cell rows are showing empty.
I figured it by adding
contentView.addSubview(titleLabel)
where cell values are being set.

Xcode - Swift - UILabel in TableViewCell Expansion

I have a UILabel in a custom UITableViewCell called "ScheduleCell", like so:
Here is the code for ScheduleCell:
import UIKit
class ScheduleCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I want the tileLabel to expand based on its text and I want the ScheduleCell to expand with it. Here are the attributes of the titleLabel:
Here are the attributes of the cell prototype, which is linked to the ScheduleCell class:
Here is some relevant code for the ViewController:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
And
func tableView(tableView: UITableView!,cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ScheduleCell
cell.backgroundColor = UIColor(rgb: 0xF6FBFE)
cell.titleLabel.text = (Globals.scheduleArr[indexPath.row][3] as! String)
cell.titleLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
return cell
}
All of the titles from "Globals.scheduleArr" fit on one line except one, which is "Jane Richards Grey Reads "To Kill a Mockingbird"". Here is what the ViewController looks like in the iOS Simulator:
As you can see, the label does not expand as necessary. Strangely enough, when I add a "\n" to the end of each of the titles, the label does expand. However, the cell doesn't seem to expand with the label:
Any help in resolving this issue would be greatly appreciated!
If you want to use UITableViewAutomaticDimension, you need to add Label-ContentView bottom constraint.
Add new bottom constraint and try again!

Swift custom UITableViewCell label is always nil

I've been stuck with this problem for days, so I'd be really happy if someone could help.
I'm trying to create a dynamic UITableView, for which I created a custom UITableView subclass and I've created a custom UITableViewCell subclass as well, because I need several UILabels and a UIButton in each cell.
The cell is created, but the problem is that the value of the labels is always nil, hence the cell isn't displayed properly.
This is, how the storyboard looks like, and this is what I see while running the program.
Here's my UITableViewCell subclass:
import UIKit
class QuestionTableViewCell: UITableViewCell {
#IBOutlet var student: UILabel!
#IBOutlet var labDesk: UILabel!
#IBOutlet var topic: UILabel!
#IBOutlet var answers: UILabel!
}
and my UITableView subclass:
import UIKit
class QuestionViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var table: UITableView!
struct Question {
var student: String
var labDesk: String
var topic: String
var answered: String
}
override func viewDidLoad() {
super.viewDidLoad()
table.estimatedRowHeight = 50
table.dataSource = self
table.delegate = self
self.table.registerClass(QuestionTableViewCell.self, forCellReuseIdentifier: "cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as QuestionTableViewCell
cell.student.text = "random string"
cell.labDesk?.text = "25/A"
cell.topic?.text = "string"
cell.answers?.text = "3"
return cell
}
}
Try removing self.table.registerClass(QuestionTableViewCell.self, forCellReuseIdentifier: "cell")
If you're using a cell with a nib then make sure that you are registering the cell with the table view using registerNib:forCellReuseIdentifier:. If the cell just has a class then use registerClass:forCellReuseIdentifier:.
First, you don't have to register the class if it exists in Interface Builder.
Second, you should dequeueReusableCellWithIdentifier:forIndexPath instead of dequeueReusableCellWithIdentifier.
Third, UITableViewController already has a property called tableView so there is no need to make an IBOutlet to table as UITableViewController already handles this. It also conforms to the UITableViewDataSource and UITableViewDataSource so these are extraneous.
Fourth, don't set the properties for table set them for tableView.
Fifth, cell.labDesk.text = "" is sufficient, no need to make it optional.
If all your IBOutlets are hooked up, Cell Identifiers correctly set, and these revisions are made, it will work.
class QuestionTableViewCell: UITableViewCell {
#IBOutlet var student: UILabel!
#IBOutlet var labDesk: UILabel!
#IBOutlet var topic: UILabel!
#IBOutlet var answers: UILabel!
}
class QuestionViewController: UITableViewController {
struct Question {
var student: String
var labDesk: String
var topic: String
var answered: String
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 50
tableView.dataSource = self
tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as QuestionTableViewCell
cell.student.text = "random string"
cell.labDesk.text = "25/A"
cell.topic.text = "string"
cell.answers.text = "3"
return cell
}
}
The most important part is to register the xib containing the custom cell with the table view. Therefore add the following code in viewDidLoad() method.
let nib = UINib.init(nibName: "MyCustomCell", bundle: nil)
self.tblUsers.register(nib, forCellReuseIdentifier: "MyCustomCell")
I might be late here, but I just solved a similar problem.
Make sure you've set the Identifier in InterfaceBuilder on your UITableViewCell.
For those who are still trying to figure this out after trying all those possible solutions:
Disconnect/Reconnect the IBOutlets in your Storyboards should do the trick!
Don't forget to add:
tableView?.register(UINib(nibName: "xyz",
bundle: nil),
forCellReuseIdentifier: "abc")
If you are using a table cell with Xib. you need to register your cell with ..
register(_:forCellReuseIdentifier:)
If you haven't added constraints for the label then they will not be created though the custom cell is created.
Make sure you added some constraints.
Make sure that the selected cell is in the right "module" and if necessary, inherit:
If not, your IBOutlets will be nil.
Issue I was facing: TableViewCell has been created and all the IBOutlets are nil. So I can't set any values such as text or color etc. Below code worked for me.
Xcode version: 13.3
Step 1:
Remove datasource and delegate reference form storyboard.
Step 2:
In viewDidLoad add,
tableView.delegate = self
tableView.dataSource = self
Step 3:
In tableview UITableViewDataSource cellForRowAt function, add your cell the given way.
let cell = tableView.dequeueCell(ofType: YourCellName.self)
cell.yourCellFunction()
return cell
Note 1: dequeueCell(ofType...) is calling the below function internally. you don't need to use it directly.
func dequeueCell<T: UITableViewCell>(ofType type: T.Type) -> T {
}
Important: You don't need to provide any "Resporation ID" or "Reuse Identifier" for cell. It works with your cell name.

Add Label To Cell on Certain Rows only

I have a lot of rows with a few different layouts - that all works fine. Now I want to add a Custom UILabel on just some of the rows. I know that there is a "problem" working with a reuse identifier, so the UITableView will try to reuse my UILabel on the next Cells again. To prevent this, I've checked a lot of suggestions here and tried it this way:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
var cell = tableView .dequeueReusableCellWithIdentifier(myQuestions.getQuestion(indexPath.row).qTemplate, forIndexPath: indexPath) as CustomTableViewCell
// if there is a saved Notice, add this to this Cell
if(myQuestions.getQuestion(indexPath.row).qNotice != nil) {
cell.addNoticeToCell("This is a Test")
}
return cell
}
And my CustomTableViewCell class is:
class CustomTableViewCell: UITableViewCell {
#IBOutlet var LabelCellTitle: UILabel!
#IBOutlet var TextView: UITextView!
#IBOutlet weak var LabelCellContent: UILabel!
var noticeButton:UIButton!
func addNoticeToCell(noticeText: String) {
noticeButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
noticeButton.frame = CGRectMake(330,44,600,44)
noticeButton.titleLabel.textColor = UIColor.blueColor()
noticeButton.tag = 100
noticeButton.setTitle(noticeText, forState: UIControlState.Normal)
self.contentView.addSubview(noticeButton)
}
override func prepareForReuse() {
println("CELL BEFORE REUSE")
if(self.contentView.subviews.count > 0) {
for mySubView in self.contentView.subviews {
if mySubView.tag == 100 {
mySubView.removeFromSuperview()
....
It works now as expected - but I don't know if its a good idea to go through all subviews. Is there a better way? I would add 2-3 UIImageViews on certain cells too, and would create it with the same procedure.
In prepareForReuse(), why not just use the property you added to your cell class to remove it rather than enumerating all of the subviews like so:
var noticeButton:UIButton?
override func prepareForReuse() {
super.prepareForReuse()
if let notice = self.noticeButton {
notice.removeFromSuperview()
self.notice = nil
}
}

Resources