Unable To Create a Custom Table View Cell programatically, Swift - swift

I have followed this stackoverflow question & answer as guide to create a custom cell inside table view (without adding a prototype cell in the storyboard), However I was not able to create a table view with the custom cells, I just get rows of empty cells. I did look at other SO questions but they did not help.
I believe I am doing something wrong with the way I register the cell.
(FYI: When I add a prototype cell to my table view and assign it a class and cell id and if I then disable tableView.register(AppointmentTVCell.self, forCellReuseIdentifier: cellId) it works, I am not interested in this since I want to minimise the use of IB)
Below is my tableViewController in storyboard, to which a table view class AppointmentsTVC has been assigned
Below is my code for the Table View Controller
class AppointmentsTVC: UITableViewController {
let cellId = "reuseIdentifier"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(AppointmentTVCell.self, forCellReuseIdentifier: cellId)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cell for Row at func called")
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! AppointmentTVCell // Creating a cell
cell.caseId.text = "123456"
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150.0
}
}
Below is my code for the custom table view cell
class AppointmentTVCell: UITableViewCell {
let caseId: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.frame = CGRect(x: 0, y: 50, width: 100, height: 30)
label.numberOfLines = 2
//label.text = "Hello"
label.textColor = UIColor.black
return label
}()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
contentView.addSubview(caseId)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

use the didMoveToSuperview method
class AppointmentTVCell: UITableViewCell {
let caseId: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.frame = CGRect(x: 0, y: 50, width: 100, height: 30)
label.numberOfLines = 2
//label.text = "Hello"
label.textColor = UIColor.black
return label
}()
override func didMoveToSuperview() {
super.didMoveToSuperview()
if superview != nil {
contentView.addSubview(caseId)
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

I have come to know that without a NIB file, awakeFromNib won't be invoked, so the view wasn't being added.
By looking at this Stackoverflow question I modified the code to the below and it worked.
class AppointmentTVCell: UITableViewCell {
var caseId = UILabel()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
//Do your cell set up
caseId.frame = CGRect(x: 0, y: 50, width: 100, height: 30)
caseId.text = "Hello"
caseId.font = UIFont(name:"HelveticaNeue-Bold", size: 16)
contentView.addSubview(caseId)
}
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
}
}

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.

Custom Table View outlets throwing nil

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
}

How I can pin the tableHeaderView?

Tell me, please, how I can pin the tableHeaderView on the ViewController's screen through code? I use this code, but the tableViewHeader disappears on scrolling:
import UIKit
class TestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableViewTest = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
createTable()
}
private func createTable() {
self.tableViewTest = UITableView(frame: view.bounds, style: .grouped)
tableViewTest.register(TestTableViewCell.self, forCellReuseIdentifier: "Test")
self.tableViewTest.delegate = self
self.tableViewTest.dataSource = self
tableViewTest.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableViewTest.separatorInset.left = 10
tableViewTest.separatorInset.right = 10
tableViewTest.tableHeaderView = "Test Header"
view.addSubview(tableViewTest)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Test", for: indexPath) as! TestTableViewCell
cell.testLabel.text = "test label"
return cell
}
}
This is my second class:
import UIKit
class TestTableViewCell: UITableViewCell {
let testLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
testLabel.frame = CGRect(x: 60, y: 5, width: UIScreen.main.bounds.width - 80, height: 50)
testLabel.numberOfLines = 0
testLabel.sizeToFit()
addSubview(testLabel)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Try to use the implicit construct of UITableView
lazy var tableViewTest = UITableView(frame: .zero, style: .plain)
But also you need to change your table header to section header which you can define in UITableViewDelegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifier = YourSectionHeaderView.reuseIdentifier
let headerView =
tableView.dequeueReusableHeaderFooterView(withIdentifier: identifier)
return headerView
}
By the way, I also recommend you to use constraint instead of autoresizing mask
Thank you.
This work for me:
import UIKit
class TestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableViewTest = UITableView()
var segmentedControl = UISegmentedControl(items: ["Test1", "Test2", "Test3"])
override func viewDidLoad() {
super.viewDidLoad()
createTable()
configureSegmentedControl()
}
private func createTable() {
self.tableViewTest = UITableView(frame: view.bounds, style: .plain)
tableViewTest.register(TestTableViewCell.self, forCellReuseIdentifier: "Test")
self.tableViewTest.delegate = self
self.tableViewTest.dataSource = self
tableViewTest.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableViewTest.separatorInset.left = 10
tableViewTest.separatorInset.right = 10
tableViewTest.tableFooterView = UIView()
view.addSubview(tableViewTest)
}
private func configureSegmentedControl() {
segmentedControl.selectedSegmentIndex = 0
segmentedControl.frame = CGRect(x: 10, y: 150, width: UIScreen.main.bounds.width - 20.0, height: 40)
segmentedControl.layer.cornerRadius = 5.0
segmentedControl.backgroundColor = .blue
segmentedControl.selectedSegmentTintColor = .red
segmentedControl.tintColor = .white
segmentedControl.addTarget(self, action: #selector(changeSegment), for: .valueChanged)
}
#objc func changeSegment(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
print("Test1")
case 1:
print("Test2")
default:
print("Test3")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Test", for: indexPath) as! TestTableViewCell
cell.testLabel.text = "test label"
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return segmentedControl
}
}

Value of type `UITableViewCell´ har no member `MyNote`

I get this error
Value of type UITableViewCell has no member MyNote
on this row line:
cell.NyNote = cell.textLabel?.text
Dont know what to do please help me out!
import UIKit
class ViewController: UITableViewController {
let array = ["item1", "item2", "item3"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.rowHeight = 70
tableView.backgroundView = UIImageView(image: UIImage(named: "concrete_seamless"))
}
// navigation bar animated
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.navigationBar.alpha = 0.5
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customcell")! as UITableViewCell
if(indexPath.item % 2 == 0){
cell.backgroundColor = UIColor.clear
}
else{
cell.backgroundColor = UIColor.white
UIColor.white.withAlphaComponent(0.2)
cell.textLabel?.backgroundColor = UIColor.white
UIColor.white.withAlphaComponent(0.0)
}
// text color
cell.textLabel?.textColor = UIColor.darkGray
cell.textLabel?.text = array[indexPath.item]
cell.NyNote = cell.textLabel?.text
return cell
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "detailview") {
let cell = sender as! customcell
let detailview = segue.destination as! DetailViewController
detailview.preMyNotes = cell.NyNote
}
}
}
And here is the var MyNotes:
import UIKit
class customcell: UITableViewCell {
var NyNote:String?
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
}
}
You have to cast the cell to your subclass, otherwise the compiler doesn't know about it. Change this line
let cell = tableView.dequeueReusableCell(withIdentifier: "customcell")! as UITableViewCell
to
let cell = tableView.dequeueReusableCell(withIdentifier: "customcell")! as customcell

UITableViewCell with a UIView subclass creates multiple layers on cell

I have an custom UITableViewCell created with (.xib) and have UIView subclass instance inside cell created from code
table delegate method :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCellOne", forIndexPath: indexPath) as! CustomOneCell
let woView = ModuleView(frame: CGRect(x: 10, y: 10, width: 400, height: 400))
woView.addBehavior(self.modulesForSubjects[indexPath.row])
// remove subviews
for subview in cell.subviews{
subview.removeFromSuperview();
}
// then add your view
cell.wiView = woView
return cell
}
my full UIView subclass :
class ModuleView: UIView {
override init (frame : CGRect) {
super.init(frame : frame)
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func addBehavior (ArrayOfmodulesForSubject: [SubjectsModules]) {
var number :CGFloat = 30
var number2 :CGFloat = 30
print(ArrayOfmodulesForSubject.count)
for (i, index) in ArrayOfmodulesForSubject.enumerate() {
let yourLabel1 = UILabel(frame: CGRectMake(number, 40 , 35 , 30))
let yourLabel2 = UILabel(frame: CGRectMake(number2, 60 , 140 , 30))
yourLabel1.textColor = UIColor.whiteColor()
yourLabel1.backgroundColor = UIColor.redColor()
yourLabel1.text = String(index.score)
yourLabel2.textColor = UIColor.whiteColor()
yourLabel2.backgroundColor = UIColor.blackColor()
yourLabel2.text = index.date
self.addSubview(yourLabel1)
self.addSubview(yourLabel2)
number += 30
number2 += 70
}
}
here is what i get after some scrolling:
UPDATE rnsjtngus solution, unfortunately also didn't helped (maybe im doing something wrong)
class CustomOneCell: UITableViewCell {
var arr = [SubjectsModules]()
#IBOutlet weak var wiView: ModuleView!
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
// Initialize your custom cell
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setupWoView(self.arr)
}
override func awakeFromNib() {
print("AWAKED FROM NIB")
for subview in subviews{
subview.removeFromSuperview()
if superview != nil {
superview!.removeFromSuperview()
}
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupWoView(arrayOfmodulesForSubject: [SubjectsModules]) {
let woView = ModuleView(frame: CGRect(x: 10, y: 10, width: 400, height: 400))
woView.backgroundColor = UIColor.blueColor()
self.addSubview(woView)
}
}
Simple but, bad solution is
just remove subviews from cell before you add your own view
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCellOne", forIndexPath: indexPath) as! CustomOneCell
let woView = ModuleView(frame: CGRect(x: 10, y: 10, width: 400, height: 400))
woView.addBehavior(self.modulesForSubjects[indexPath.row])
// remove subviews
for subview in cell.subviews{
subview.removeFromSuperview();
}
// then add your view
cell.addSubview(woView)
return cell
}
When you call dequeue the method dequeues an existing cell if one is available or creates a new one using the class or nib if not.
This means you may well get one that has already had a subview added to it and when you dequeue it again your code would then add a second one. You need to start by removing any existing subviews.