Custom Table View outlets throwing nil - swift

I am trying to implement a simple iOS app using the VIPER pattern following this tutorial. The app is a simple table view which displays data from a json req
I have created the table view and can successfully show data from my object using the default cell.textLabel. I am however trying to create a custom table view cell and so created the nib and class. I have connected all the outlets up correctly to the class from the nib and the code for this class is as follows:
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomTableView"
static func nib() -> UINib {
return UINib(nibName: "CustomTableViewCell", bundle: nil)
}
#IBOutlet var level: UILabel!
#IBOutlet var levelNumber: UILabel!
#IBOutlet var progress: UIProgressView!
#IBOutlet var leftProgress: UILabel!
#IBOutlet var rightProgress: UILabel!
public func configure(levelString: String, levelNum: String, prog: Float, left: String, right: String) {
level.text = levelString
levelNumber.text = levelNum
progress.setProgress(prog, animated: true)
leftProgress.text = left
rightProgress.text = right
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
The problem is, when I run the app, these outlets are erroring: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value on level.text inside the configure function. I can suppress this error by doing this in my view:
table.register(CustomTableViewCell.nib(), forCellReuseIdentifier: CustomTableViewCell.identifier) however this just shows an empty tableView without the custom rows or any data. If I do table.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier) then this crashes. What way should I be doing it and how can I get the data to display in the tableView? relevant code added below:
let tableView: UITableView = {
let table = UITableView()
table.register(CustomTableViewCell.nib(), forCellReuseIdentifier: CustomTableViewCell.identifier)
table.isHidden = true
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
label.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
label.center = view.center
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return achievements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(levelString: "Level",
levelNum: achievements[indexPath.row].level,
prog: Float(achievements[indexPath.row].progress/achievements[indexPath.row].total),
left: String(achievements[indexPath.row].progress),
right: String(achievements[indexPath.row].total))
return cell
}
Outlet Setup:
Files Owner:

I appreciate this might not be the correct answer for everyone, but this is what worked for me. I was struggling with trying to debug this and for the sake of time, I concluded that just creating everything programmatically would be easier. There are numerous tutorials for this, but I took the following structure:
Stevia library used to set constraints for components and add subview
import Stevia
import UIKit
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomTableViewCell"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupFieldsAndConstraints()
}
func setupFieldsAndConstraints() {
subviews {
customLabel
}
setupCustomLabel()
}
var customLabel: UILabel = {
let level = UILabel()
return level
}()
func setupCustomLabel() {
customLabel.style { l in
l.textAlignment = .left
l.textColor = .white
l.Top == 50
l.Left == 20
}
}
func setValues(object: Object) {
label.text = object.name
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And this is referenced in your view as it normally would be. I chose to add mine to a function that encapsulates any table configuration and then called in viewDidLoad:
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
And then for the cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier) as! CustomTableViewCell
cell.setValues(object: Object[indexPath.row])
return cell
}

Related

Programmatic swift: TableView inside a TableView

I'm currently trying to put a tableview inside a tableview, but programmatically (in order to understand better wtf I'm doing in the storyboard way of doing this). I'm currently stuck at showing the inner table, any help please?
class ContactsViewController: UIViewController, UITabBarDelegate,
UITableViewDataSource, UITableViewDelegate {
private let contacts = ContactAPI.getData()
let contactsTableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(contactsTableView)
contactsTableView.translatesAutoresizingMaskIntoConstraints = false // enable Auto-Layout
contactsTableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contactsTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contactsTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
contactsTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contactsTableView.dataSource = self
contactsTableView.delegate = self
contactsTableView.register(ContactsCell.self, forCellReuseIdentifier: "contactCell")
contactsTableView.rowHeight = 1000
contactsTableView.estimatedRowHeight = 100
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactsCell
cell.jobLabel.text = contacts[indexPath.row].jobTitle
cell.nameLabel.text = contacts[indexPath.row].name
return cell
}
}
The external TableViewCell is given by
class ContactsCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(image)
contentView.addSubview(nameLabel)
contentView.addSubview(jobLabel)
contentView.addSubview(tablello)
}
// working code that defines image, nameLabel, jobLabel
let tablello: UITableView = {
let tab = UITableView()
let data = innerTable()
tab.dataSource = data
tab.delegate = data
tab.frame = CGRect (x: 0, y: 0, width: 300, height: 300)
tab.register(gInnerCell.self, forCellReuseIdentifier: "gennaroCell")
tab.translatesAutoresizingMaskIntoConstraints = false
return tab
}()
}
And relative classes
class innerTable: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gennaroCell", for: indexPath) as! gInnerCell
cell.labelloNapoli.text = "\(indexPath.row)"
print("AO GENNà")
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("loaded inner")
}
}
class gInnerCell: UITableViewCell {
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
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(labelloNapoli)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let labelloNapoli:UILabel = {
let lable = UILabel()
lable.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
return lable
}()
}
I've tried turning tablello into a separate View, and adding there the table as a subview, but (although it printed the inside print() ) it messes up all my UI.
In general what are you trying to accomplish by putting a tableView inside of another tableView? Are you able to display the same information using a custom tableViewCell? Or better yet use a CollectionView as they are very flexible when it comes to UI. Sorry for not answering the question; Just trying to get a better understanding of what you are trying to accomplish UI wise.

Dynamically resize UITableViewCell at runtime on tvOS

I currently have a UIViewController with the following hierarchy:
UIViewController
-- UIView
---- UITableView
------ UITableViewCell
-------- UICollectionView
---------- UICollectionViewCell
-------- UIView
---------- UIStackView
------------ UILabel
------------ UILabel
So basically, I have a UITableViewCell, containing an UICollectionView and an UIView, with the following constraints defined in Interface Builder:
UICollectionView.leading = Superview.leading
UICollectionView.trailing = Superview.trailing
UICollectionView.top = Superview.top
UICollectionView.bottom = UIView.top
UIView.leading = Superview.leading
UIView.trailing = Superview.trailing
UIView.bottom = Superview.bottom
The UICollectionView is also set up with a horizontal flow layout.
In my ViewController, I have also overridden the heightForRowAtIndexPath datasource function for the UITableView.
The result is a vertically scrolling list of UITableViewCells, and each UITableViewCell will have a horizontally scrolling list of UIImageViews. The UIView and its two UILabels will display relevant information to the UIImageView when it is focused.
What I am trying to achieve is to hide the UIView containing the two UILabels when the focus moves to a different UITableViewCell. I am able to trap and detect the change in focus by overriding the didUpdateFocusInContext function. I am able to reduce the height of the previously-focused UITableViewCell in the UICollectionViewDelegate, however, the gap between the previously-focused and currently-focused UITableViewCells remain unchanged.
Attempts to set the UITableView.RowHeight to UITableViewAutomaticDimension, setting UITableView.EstimatedRowHeight to an arbitrary figure, removing the overridden heightForRowAtIndexPath function, causes the UICollectionViews to not load at all.
Does anyone have a good suggestion on what I could try?
Edit: Sample source code
ViewController
// ==== ViewController.swift ====
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
let cellIdentifier = "\(CVTableViewCell.self)"
override func viewDidLoad() {
super.viewDidLoad()
setupControls()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
setupControls()
}
func setupControls() {
setupTableView()
}
func setupTableView() {
tableView.dataSource = self
tableView.delegate = self
// causes collectionView in CVTableViewCell to not load
// tableView.rowHeight = UITableViewAutomaticDimension
// tableView.estimatedRowHeight = 200
tableView.register(UINib(nibName: cellIdentifier, bundle: nil),
forCellReuseIdentifier: cellIdentifier)
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 316.0 + 106.0 // 316.0 for image height, 106.0 for labelContainerView
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let dequeuedCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? CVTableViewCell else {
fatalError("Unable to dequeue cell")
}
return dequeuedCell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool {
return false
}
}
CVTableViewCell
// ==== CVTableViewCell.swift ====
import Foundation
import UIKit
class CVTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var labelContainerView: UIView!
#IBOutlet weak var labelContainerStackView: UIStackView!
#IBOutlet weak var firstLabel: UILabel!
#IBOutlet weak var secondLabel: UILabel!
let cellIdentifier = "\(CVCollectionViewCell.self)"
override func awakeFromNib() {
super.awakeFromNib()
setupControls()
}
override func prepareForReuse() {
super.prepareForReuse()
setupControls()
}
func setupControls() {
setupTableViewCell()
setupCollectionView()
setupLabelContainerView()
setupLabelContainerStackView()
setupLabels()
}
func setupTableViewCell() {
contentView.backgroundColor = .clear
}
func setupCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 316.0, height: 316.0)
layout.minimumInteritemSpacing = 0.0
layout.minimumLineSpacing = 50.0
collectionView.dataSource = self
collectionView.delegate = self
collectionView.backgroundColor = .clear
collectionView.clipsToBounds = false
collectionView.collectionViewLayout = layout
collectionView.register(UINib(nibName: cellIdentifier, bundle: nil),
forCellWithReuseIdentifier: cellIdentifier)
}
func setupLabelContainerView() {
labelContainerView.backgroundColor = .clear
}
func setupLabelContainerStackView() {
labelContainerStackView.backgroundColor = .clear
labelContainerStackView.distribution = .fillEqually
}
func setupLabels() {
firstLabel.text = "Look at me"
secondLabel.text = "I refuse to go away"
}
}
extension CVTableViewCell: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let dequeuedCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? CVCollectionViewCell else {
fatalError("Unable to dequeue cell")
}
dequeuedCell.imageView.adjustsImageWhenAncestorFocused = true
dequeuedCell.imageView.image = #imageLiteral(resourceName: "stackoverflow")
return dequeuedCell
}
}
extension CVTableViewCell: UICollectionViewDelegate {
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocus(in: context, with: coordinator)
// if next focused view is within current collectionview, show labelContainerView
// else hide
if let cell = context.nextFocusedView as? CVCollectionViewCell,
let _ = collectionView.indexPath(for: cell) {
labelContainerView.isHidden = false
} else {
labelContainerView.isHidden = true
}
}
}
CVCollectionViewCell
// ==== CVCollectionViewCell.swift ====
import Foundation
import UIKit
class CVCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
setupControls()
}
override func prepareForReuse() {
super.prepareForReuse()
setupControls()
}
func setupControls() {
setupImageView()
}
func setupCollectionViewCell() {
backgroundColor = .clear
}
func setupImageView() {
imageView.image = nil
}
}

Protocol UITableViewDataSource is not called from xib - Swift

I don't know why my protocol UITableViewDataSource is not execute when i run the APP.
Here my xib design:
My init of class from xib is: CustomSearchDestination
#IBOutlet weak var searchText: UITextField!
#IBOutlet weak var tableView: UITableView!
let xibName = "CustomSearchDestination"
var destinationListLocal: [Destinos] = []
var SearchViewControllerView: SearchViewController?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
xibSetup(xibName)
SearchViewControllerView = SearchViewController()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
searchText.delegate = self
tableView.delegate = self
tableView.dataSource = self
}
override init(frame: CGRect) {
super.init(frame : frame)
xibSetup(xibName)
}
Inside of this class have implemented the protocol UITableViewDelegate and UITableViewDataSource
extension CustomSearchDestination: UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("El count es: \(destinationListLocal.count)")
return destinationListLocal.count
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell: UITableViewCell = tableView.cellForRow(at: indexPath)!
searchText.text = selectedCell.textLabel!.text
}}
extension CustomSearchDestination: UITableViewDataSource {
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
let index = indexPath.row as Int
if destinationListLocal.count == 0 {
if let str = destinationListLocal[index].desDestino {
cell.textLabel?.text = str
}
}
return cell
}}
Can someone point me my mistake?
EDIT: My hierarchy is:
I have a xib where i put other xib like this:
this is my class CustomViewHandler. So.. my class CustomSearchDestination is a class of the superClass CustomViewHandler.
At the same time is a class of CustomSearch
class CustomViewHandler : UIView {
var view: UIView!
var controllerView : UIView!
// Keyboard
var activeField : UITextField?
var isKeyboardVisible = false
var fixValue : CGFloat?
var keyboardSize : CGFloat = 280
fileprivate func loadViewFromNib(_ nibName: String) -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
// Assumes UIView is top level and only object in CustomView.xib file
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
func xibSetup(_ nibName: String) {
view = loadViewFromNib(nibName)
// use bounds not frame or it'll be offset
view.frame = bounds
// Makes the view stretch with containing view
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
}}

Two UITableViews in one UIViewController with custom prototype cells gives errors

im learning swift and i am trying to build an app for logging workouts.
I'm using diefferent views and prototype cells and it works perfect.
But now on one view i have two tableviews, with each of them has the same kind of prototype cell, which is also a custom cell:
import UIKit
class muscleCell: UITableViewCell {
#IBOutlet weak var lblDescription: UILabel!
var id : Int64?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
It has just an ID and a label. I have used it in another tableview and it works perfect.
I have found here on stackoverflow how to use multiple UITableViews in one View and it showed me the data perfectly with the standard cell.
But as soon as i add the identifier in the storyboard, and make the outlet connections and use these identifiers in the code i get an error.
a quick info, i have tried this line:
self.lvMainMuscles.register(UITableViewCell.self, forCellReuseIdentifier: "mainMuscleCell")
also like this (cause i found it somehwere that it might help):
self.lvMainMuscles.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
but did also not work.
import UIKit
class MachineController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var txtName: UITextField!
#IBOutlet weak var txtType: UITextField!
#IBOutlet weak var deleteToolbar: UIToolbar!
#IBOutlet weak var btnDelete: UIBarButtonItem!
#IBOutlet weak var lvMainMuscles: UITableView!
#IBOutlet weak var lvSupportingMuscles: UITableView!
var machineId: Int64?
var objMachine = Machine(connection: wpdb().db!)
var objMuscle = Muscle(connection: wpdb().db!)
var mainMuscleData : Array<Muscle.structMuscleList> = []
var supportingMuscleData : Array<Muscle.structMuscleList> = []
override func viewDidLoad() {
super.viewDidLoad()
if (machineId != nil) {
if (objMachine.loadById(Id: machineId!) == true) {
self.title = objMachine.name
txtName.text = objMachine.name
txtType.text = String(objMachine.typeId)
}
} else {
deleteToolbar.isHidden = true
}
lvMainMuscles.delegate = self
lvMainMuscles.dataSource = self
lvMainMuscles.allowsMultipleSelection = true
self.lvMainMuscles.register(UITableViewCell.self, forCellReuseIdentifier: "mainMuscleCell")
lvSupportingMuscles.delegate = self
lvSupportingMuscles.dataSource = self
lvSupportingMuscles.allowsMultipleSelection = true
self.lvSupportingMuscles.register(UITableViewCell.self, forCellReuseIdentifier: "supportingMuscleCell")
getData()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnSaveClick(_ sender: Any) {
objMachine.name = txtName.text!
objMachine.isSystem = false
objMachine.typeId = Int64(txtType.text!)!
let returnId = objMachine.save()
print("edited/saved id: \(returnId)")
let selectedrows = lvMainMuscles.indexPathsForSelectedRows
if (selectedrows?.count)! > 0 {
for row in selectedrows! {
print(row.row)
}
}
_ = self.navigationController?.popViewController(animated: true)
}
#IBAction func btnDeleteClick(_ sender: Any) {
let alert = UIAlertController(title: title, message: "Do you really want to delete the machine?", preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Delete", style: .default, handler: { action in
if self.objMachine.delete() == true {
_ = self.navigationController?.popViewController(animated: true)
}
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: nil)
alert.addAction(cancelAction)
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
func getData() {
mainMuscleData = objMuscle.getList()
supportingMuscleData = objMuscle.getList()
// searchTextField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.lvMainMuscles {
return mainMuscleData.count
} else {
return supportingMuscleData.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:muscleCell!
if tableView == self.lvMainMuscles {
cell = tableView.dequeueReusableCell(withIdentifier: "mainMuscleCell") as! muscleCell
cell.lblDescription.text = mainMuscleData[indexPath.row].description
}
if tableView == self.lvSupportingMuscles {
cell = tableView.dequeueReusableCell(withIdentifier: "supportingMuscleCell") as! muscleCell
cell.lblDescription.text = supportingMuscleData[indexPath.row].description
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// tableView.deselectRow(at: indexPath as IndexPath, animated: true)
if tableView == lvMainMuscles {
let cell = self.lvMainMuscles.cellForRow(at: indexPath)!
cell.accessoryType = UITableViewCellAccessoryType.checkmark;
} else {
let cell = self.lvSupportingMuscles.cellForRow(at: indexPath)!
cell.accessoryType = UITableViewCellAccessoryType.checkmark;
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if tableView == lvMainMuscles {
let cell = self.lvMainMuscles.cellForRow(at: indexPath)!
cell.accessoryType = UITableViewCellAccessoryType.none;
} else {
let cell = self.lvSupportingMuscles.cellForRow(at: indexPath)!
cell.accessoryType = UITableViewCellAccessoryType.none;
}
}
the error I get is:
Could not cast value of type 'UITableViewCell' (0x10a63c778) to
'WorkoutPartner.muscleCell' (0x107b422f0).
in this line:
cell = tableView.dequeueReusableCell(withIdentifier: "supportingMuscleCell") as! muscleCell
I don't understand why, since I am using the same cell in another view and it works.
i hope you have got an idea,
thank you very much
JYB
From your question and code what I understood is that the two tables have same design for the cells in same UIView.
In this case whenever a cell design is reused, we should make an XIB of your custom table cell, register that NIB with table and then deque in cellForRow
Here is a quick Sample I made for you.
Here is the controller code part:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var firstTable: UITableView!
#IBOutlet weak var secondTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
firstTable.dataSource = self
firstTable.delegate = self
secondTable.dataSource = self
secondTable.delegate = self
//Registering Cell with tables
self.firstTable.register(UINib(nibName: "MuscleTableCell", bundle: nil), forCellReuseIdentifier: "MuscleTableCell")
self.secondTable.register(UINib(nibName: "MuscleTableCell", bundle: nil), forCellReuseIdentifier: "MuscleTableCell")
}
}
extension ViewController : UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.firstTable {
return 5
} else {
return 2
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.firstTable {
let cell = tableView.dequeueReusableCell(withIdentifier: "MuscleTableCell") as! MuscleTableCell
cell.descriptionLabel.text = "Muscle Table Cell"
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "MuscleTableCell") as! MuscleTableCell
cell.descriptionLabel.text = "Supporting Table Cell"
return cell
}
}
}
The CustomCellClass
import UIKit
class MuscleTableCell: UITableViewCell {
#IBOutlet weak var descriptionLabel: 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
}
}
The Custom Cell have its nib file too.
And the Output:
You have registered UITableViewCell.self for the UITableView to use, therefore, it is returning that. Try registering your custom cell class instead.
OK if someone has the same problem:
I solved it by removing these two lines of code:
self.lvMainMuscles.register(UITableViewCell.self, forCellReuseIdentifier: "mainMuscleCell")
and
self.lvSupportingMuscles.register(UITableViewCell.self, forCellReuseIdentifier: "supportingMuscleCell")
I think my error was, that I registered the cell although I have a prototype cell in my storyboard.

creating custom tableview cells in swift

I have a custom cell class with a couple of IBOutlets. I have added the class to the storyboard. I have connected all my outlets. my cellForRowAtIndexPath function looks like this:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as SwipeableCell
cell.mainTextLabel.text = self.venueService.mainCategoriesArray()[indexPath.row]
return cell
}
Here is my custom cell class:
class SwipeableCell: UITableViewCell {
#IBOutlet var option1: UIButton
#IBOutlet var option2: UIButton
#IBOutlet var topLayerView : UIView
#IBOutlet var mainTextLabel : UILabel
#IBOutlet var categoryIcon : UIImageView
init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
}
When I run the app, all my cell are empty. I have logged out self.venueService.mainCategoriesArray() and it contains all the correct strings. I have also tried putting an actual string equal to the label, and that produces the same result.
What am I missing? Any help is appreciated.
Custom Table View Cell Example
Tested with Xcode 9 (edit also tested on 11 / 12 Beta 2) and Swift 4 (edit: also tested on 5.2)
The asker of the original question has solved their problem. I am adding this answer as a mini self contained example project for others who are trying to do the same thing.
The finished project should look like this:
Create a new project
It can be just a Single View Application.
Add the code
Add a new Swift file to your project. Name it MyCustomCell.swift. This class will hold the outlets for the views that you add to your cell in the storyboard.
import UIKit
class MyCustomCell: UITableViewCell {
#IBOutlet weak var myView: UIView!
#IBOutlet weak var myCellLabel: UILabel!
}
We will connect these outlets later.
Open ViewController.swift and make sure you have the following content:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
// These are the colors of the square views in our table view cells.
// In a real project you might use UIImages.
let colors = [UIColor.blue, UIColor.yellow, UIColor.magenta, UIColor.red, UIColor.brown]
// Don't forget to enter this in IB also
let cellReuseIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.myView.backgroundColor = self.colors[indexPath.row]
cell.myCellLabel.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Setup the storyboard
Add a Table View to your view controller and use auto layout to pin it to the four sides of the View Controller. Then drag a Table View Cell onto the Table View. And then drag a View and a Label onto the Prototype cell. (You may need to select the Table View Cell and manually set the Row Height to something taller in the Size inspector so that you have more room to work with.) Use auto layout to fix the View and the Label how you want them arranged within the content view of the Table View Cell. For example, I made my View be 100x100.
Other IB settings
Custom class name and Identifier
Select the Table View Cell and set the custom class to be MyCustomCell (the name of the class in the Swift file we added). Also set the Identifier to be cell (the same string that we used for the cellReuseIdentifier in the code above.
Hook Up the Outlets
Control drag from the Table View in the storyboard to the tableView variable in the ViewController code.
Do the same for the View and the Label in your Prototype cell to the myView and myCellLabel variables in the MyCustomCell class.
Finished
That's it. You should be able to run your project now.
Notes
The colored views that I used here could be replaced with anything. An obvious example would be a UIImageView.
If you are just trying to get a TableView to work, see this even more basic example.
If you need a Table View with variable cell heights, see this example.
This is for who are working custom cell with .xib
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let identifier = "Custom"
var cell: CustomCell! = tableView.dequeueReusableCellWithIdentifier(identifier) as? CustomCel
if cell == nil {
tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: identifier)
cell =tableView.dequeueReusableCellWithIdentifier(identifier) as? CustomCell
}return cell}
I have the same problem.
Generally what I did is the same as you.
class dynamicCell: UITableViewCell {
#IBOutlet var testLabel : UILabel
init(style: UITableViewCellStyle, reuseIdentifier: String) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
and in the uitableviewcell method:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell :dynamicCell = tableView.dequeueReusableCellWithIdentifier("cell") as dynamicCell
cell.testLabel.text = "so sad"
println(cell.testLabel)
return cell;
}
and yeah the tableview shows nothing! But guess what, it actually shows something...because the log I get from the println(cell.testLabel) shows that all the Labels are actually displayed out.
BUT! their Frames is strange, which have something like this:
frame = (0 -21; 42 21);
so it has a (0,-21) as (x,y), so that means the label just appears at somewhere outside the bound of the cell.
so I try to add adjust the frame manually like this:
cell.testLabel.frame = CGRectMake(10, 10, 42, 21)
and sadly, it doesn't work.
---------------update after 10 min -----------------
I DID IT.
so, it seems that the problem comes from the Size Classes.
Click on your .storyboard file and go to the File Inspector Tab
UNCHECK THE Size Classes checkbox
and finally, my "so sad"Label comes out!
Thanks for all the different suggestions, but I finally figured it out. The custom class was set up correctly. All I needed to do, was in the storyboard where I choose the custom class: remove it, and select it again. It doesn't make much sense, but that ended up working for me.
Last Updated Version is with xCode 6.1
class StampInfoTableViewCell: UITableViewCell{
#IBOutlet weak var stampDate: UILabel!
#IBOutlet weak var numberText: UILabel!
override init?(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init(coder aDecoder: NSCoder) {
//fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Details
Xcode Version 10.2.1 (10E1001), Swift 5
Solution
import UIKit
// MARK: - IdentifiableCell protocol will generate cell identifier based on the class name
protocol Identifiable: class {}
extension Identifiable { static var identifier: String { return "\(self)"} }
// MARK: - Functions which will use a cell class (conforming Identifiable protocol) to `dequeueReusableCell`
extension UITableView {
typealias IdentifiableCell = UITableViewCell & Identifiable
func register<T: IdentifiableCell>(class: T.Type) { register(T.self, forCellReuseIdentifier: T.identifier) }
func register(classes: [Identifiable.Type]) { classes.forEach { register($0.self, forCellReuseIdentifier: $0.identifier) } }
func dequeueReusableCell<T: IdentifiableCell>(aClass: T.Type, initital closure: ((T) -> Void)?) -> UITableViewCell {
guard let cell = dequeueReusableCell(withIdentifier: T.identifier) as? T else { return UITableViewCell() }
closure?(cell)
return cell
}
func dequeueReusableCell<T: IdentifiableCell>(aClass: T.Type, for indexPath: IndexPath, initital closure: ((T) -> Void)?) -> UITableViewCell {
guard let cell = dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as? T else { return UITableViewCell() }
closure?(cell)
return cell
}
}
extension Array where Element == UITableViewCell.Type {
var onlyIdentifiables: [Identifiable.Type] { return compactMap { $0 as? Identifiable.Type } }
}
Usage
// Define cells classes
class TableViewCell1: UITableViewCell, Identifiable { /*....*/ }
class TableViewCell2: TableViewCell1 { /*....*/ }
// .....
// Register cells
tableView.register(classes: [TableViewCell1.self, TableViewCell2.self]. onlyIdentifiables)
// Create/Reuse cells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.row % 2) == 0 {
return tableView.dequeueReusableCell(aClass: TableViewCell1.self, for: indexPath) { cell in
// ....
}
} else {
return tableView.dequeueReusableCell(aClass: TableViewCell2.self, for: indexPath) { cell in
// ...
}
}
}
Full Sample
Do not forget to add the solution code here
import UIKit
class ViewController: UIViewController {
private weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
}
// MARK: - Setup(init) subviews
extension ViewController {
private func setupTableView() {
let tableView = UITableView()
view.addSubview(tableView)
self.tableView = tableView
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.register(classes: [TableViewCell1.self, TableViewCell2.self, TableViewCell3.self].onlyIdentifiables)
tableView.dataSource = self
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch (indexPath.row % 3) {
case 0:
return tableView.dequeueReusableCell(aClass: TableViewCell1.self, for: indexPath) { cell in
cell.textLabel?.text = "\(cell.classForCoder)"
}
case 1:
return tableView.dequeueReusableCell(aClass: TableViewCell2.self, for: indexPath) { cell in
cell.textLabel?.text = "\(cell.classForCoder)"
}
default:
return tableView.dequeueReusableCell(aClass: TableViewCell3.self, for: indexPath) { cell in
cell.textLabel?.text = "\(cell.classForCoder)"
}
}
}
}
Results
Uncheck "Size Classes" checkbox works for me as well, but you could also add the missing constraints in the interface builder. Just use the built-in function if you don't want to add the constraints on your own. Using constraints is - in my opinion - the better way because the layout is independent from the device (iPhone or iPad).
It is Purely swift notation an working for me
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cellIdentifier:String = "CustomFields"
var cell:CustomCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? CustomCell
if (cell == nil)
{
var nib:Array = NSBundle.mainBundle().loadNibNamed("CustomCell", owner: self, options: nil)
cell = nib[0] as? CustomCell
}
return cell!
}
[1] First Design your tableview cell in StoryBoard.
[2] Put below table view delegate method
//MARK: - Tableview Delegate Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return <“Your Array”>
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
var totalHeight : CGFloat = <cell name>.<label name>.frame.origin.y
totalHeight += UpdateRowHeight(<cell name>.<label name>, textToAdd: <your array>[indexPath.row])
return totalHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell : <cell name>! = tableView.dequeueReusableCellWithIdentifier(“<cell identifier>”, forIndexPath: indexPath) as! CCell_VideoCall
if(cell == nil)
{
cell = NSBundle.mainBundle().loadNibNamed("<cell identifier>", owner: self, options: nil)[0] as! <cell name>;
}
<cell name>.<label name>.text = <your array>[indexPath.row] as? String
return cell as <cell name>
}
//MARK: - Custom Methods
func UpdateRowHeight ( ViewToAdd : UILabel , textToAdd : AnyObject ) -> CGFloat{
var actualHeight : CGFloat = ViewToAdd.frame.size.height
if let strName : String? = (textToAdd as? String)
where !strName!.isEmpty
{
actualHeight = heightForView1(strName!, font: ViewToAdd.font, width: ViewToAdd.frame.size.width, DesignTimeHeight: actualHeight )
}
return actualHeight
}
Set tag for imageview and label in cell
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.tableData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("imagedataCell", forIndexPath: indexPath) as! UITableViewCell
let rowData = self.tableData[indexPath.row] as! NSDictionary
let urlString = rowData["artworkUrl60"] as? String
// Create an NSURL instance from the String URL we get from the API
let imgURL = NSURL(string: urlString!)
// Get the formatted price string for display in the subtitle
let formattedPrice = rowData["formattedPrice"] as? String
// Download an NSData representation of the image at the URL
let imgData = NSData(contentsOfURL: imgURL!)
(cell.contentView.viewWithTag(1) as! UIImageView).image = UIImage(data: imgData!)
(cell.contentView.viewWithTag(2) as! UILabel).text = rowData["trackName"] as? String
return cell
}
OR
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "imagedataCell")
if let rowData: NSDictionary = self.tableData[indexPath.row] as? NSDictionary,
urlString = rowData["artworkUrl60"] as? String,
imgURL = NSURL(string: urlString),
formattedPrice = rowData["formattedPrice"] as? String,
imgData = NSData(contentsOfURL: imgURL),
trackName = rowData["trackName"] as? String {
cell.detailTextLabel?.text = formattedPrice
cell.imageView?.image = UIImage(data: imgData)
cell.textLabel?.text = trackName
}
return cell
}
see also TableImage loader from github
The actual Apple reference documentation is quite comprehensive
https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/CreateATableView.html#//apple_ref/doc/uid/TP40015214-CH8-SW2
Scroll down until you see this part