I'm working on a table view with a custom cell that has been created programmatically, with no usage of IB. I've looked around on Google and asked around in NSChat, but I still can't get it to work. It just displays the default table view. Thanks in advance!
EventCell.swift
import UIKit class EventCell: UITableViewCell { var eventName: UILabel = UILabel() var eventCity: UILabel = UILabel() var eventTime: UILabel = UILabel() override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.contentView.addSubview(eventName) self.contentView.addSubview(eventCity) self.contentView.addSubview(eventTime) } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func layoutSubviews() { super.layoutSubviews() eventName = UILabel(frame: CGRectMake(20, 10, self.bounds.size.width - 40, 25)) eventCity = UILabel(frame: CGRectMake(0, 0, 0, 0)) eventTime = UILabel(frame: CGRectMake(0, 0, 0, 0)) } }
ViewController.swift
class ViewController: UITableViewController, UITableViewDelegate { var events: Dictionary<String, [String]> = ["0": ["Monroe Family", "La Cañada", "8:30"]] override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self; tableView.delegate = self; tableView.registerClass(EventCell.self, forCellReuseIdentifier: "EventCell") } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension; } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return events.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cellIdendifier: String = "EventCell" var cell: EventCell = tableView.dequeueReusableCellWithIdentifier(cellIdendifier, forIndexPath: indexPath) as EventCell cell = EventCell(style: .Default, reuseIdentifier: cellIdendifier) if let i = events[String(indexPath.row)] { cell.eventName.text = i[0] cell.eventCity.text = i[1] cell.eventTime.text = i[2] } cell.sizeToFit() return cell } }
Ok first a few important comments.
First, you are needlessly creating your own new cell every time the table view requests a cell instead of reusing old cells. You should remove this line:
cell = EventCell(style: .Default, reuseIdentifier: cellIdendifier)
That is unnecessary because dequeue will automatically create new cells as needed based on the class you have registered for the given identifier.
Second, you should not be using the main screen bounds when laying out your code. This will break down if your table view is not the full width. Instead, you can use self.bounds
so it is always relative to the cell itself.
Third, you should not be calling setNeedsLayout or layoutIfNeeded because if that method is called, it is already laying out everything again.
Fourth, you should register your table view cell class before setting the table view data source just in case UITableView every starts requesting things from data source when the data source is set.
Fifth, two of your subviews have a size of 0,0 so they are not going to show up anyway.
If this code is indeed running without crashing then you are creating EventCells because you are doing a forced casting from the result of the dequeueReusableCellWithIdentifier:forIndexPath
. That means you simply have a layout / display / data issue.
I had the same question. I applied the advice in @drewag's answer, but there were some additional issues. Below is a bare-bones, proof-of-concept example that shows how to use a programmatically created UITableViewCell
subclass.
The image below is what it should look like. The yellow rectangles are UILabel
subviews in the custom cells.
Here is the UITableViewCell subclass. It's job is to initialize the cell and its content, add the subviews, and layout the subviews.
import UIKit class MyCustomCell: UITableViewCell { var myLabel = UILabel() override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) myLabel.backgroundColor = UIColor.yellowColor() self.contentView.addSubview(myLabel) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func layoutSubviews() { super.layoutSubviews() myLabel.frame = CGRect(x: 20, y: 0, width: 70, height: 30) } }
Here is the view controller code.
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var tableView: UITableView! let myArray = ["one", "two", "three", "four"] let cellReuseIdendifier = "cell" override func viewDidLoad() { super.viewDidLoad() tableView.registerClass(MyCustomCell.self, forCellReuseIdentifier: cellReuseIdendifier) tableView.dataSource = self tableView.delegate = self } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return myArray.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(cellReuseIdendifier, forIndexPath: indexPath) as! MyCustomCell cell.myLabel.text = myArray[indexPath.row] return cell } }
UIViewController
rather than a UITableViewController
. If you use a UITableViewController
then remove the UITableViewDelegate
and UITableViewDataSource
protocol references since these are redundant for a UITableViewController
. You also won't need the @IBOutlet tableView
line. eventName = UILabel(frame: CGRectMake(...))
. Instead it should have been eventName.frame = CGRect(...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With