Tableview error when using custom cell class - ios

I am trying to make a tableViewController with a custom cell class programmatically but I am getting this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier cellId - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
*** First throw call stack:
I have several UItableViews in my app with custom cells and they all work fine but for some reason, this one isn't working. I've spent the last few hours trying to work it out but can't see the problem, can someone help me please?
This is my tableview class:
var tableView = UITableView()
var twoDimensionalArray = [
cameraInfoStruct(cameraInfo: ["Camera Body","Lens", "FPS", "Shutter Angle", "Colour Temperature", "Resolution Width", "Resolution Height", "Format", "Camera Rig"]),
cameraInfoStruct(cameraInfo:["Focus Length", "F - Stop", "Camera Height", "Camera Tilt", "Camera Roll", "Filter"])
]
let cellId = "cellId"
let screenHeight = UIScreen.main.bounds.height
let screenWidth = UIScreen.main.bounds.width
let sectionHeaders = ["General", "Advanced"]
override func viewDidLoad() {
super.viewDidLoad()
addTableView()
}
func addTableView() {
tableView = UITableView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
tableView.dataSource = self
tableView.delegate = self
tableView.tableFooterView = UIView()
self.view.addSubview(tableView)
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sectionHeaderArray = sectionHeaders as? [String] {
return sectionHeaderArray[section]
}
return "unknown"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return twoDimensionalArray[section].cameraInfo.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return sectionHeaders.count
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor.white
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! cameraInfoCell
let shotNumber = twoDimensionalArray[indexPath.section].cameraInfo[indexPath.row]
cell.textLabel?.text = shotNumber
return cell
}
}
And this is my custom cell class:
class cameraInfoCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
self.textLabel?.frame.origin.x = 20
self.detailTextLabel?.frame.origin.x = 20
self.detailTextLabel?.textColor = UIColor(red:0.73, green:0.73, blue:0.73, alpha:1.0)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

You use two different cellIDs:
In your class, you define a cellId variable,
let cellId = "cellId"
which you use in cellForRowAt:
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! cameraInfoCell
In addTableView, you use the hardcoded string "MyCell":
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
To fix it, use the cellId in both places, so change the code in your addTableView method to:
tableView.register(cameraInfoCell.self, forCellReuseIdentifier: cellId)

You have to register the table inviewDidLoad either
tableView.register(cellClassName.self, forCellReuseIdentifier: "cellId")

Related

cells must be retrieved by calling - dequeueReusableCellWithReuseIdentifier:forIndexPath

Problem encountered:
the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier - cells must be retrieved by calling - dequeueReusableCellWithReuseIdentifier:forIndexPath:"
In my TableViewCell, I have a CollectionViewCell.
import UIKit
protocol CollectionViewTableViewCellDelegate: AnyObject {
func collectionViewTableViewCellDidTapCell(_ cell: CollectionViewTableViewCell, viewModel: TitlePreviewViewModel)
}
class CollectionViewTableViewCell: UITableViewCell {
static let identifier = "CollectionViewTableViewCellId"
weak var delegate: CollectionViewTableViewCellDelegate?
private var titles: [Title] = [Title]()
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 140, height: 200)
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(TitleCollectionViewCell.self, forCellWithReuseIdentifier: TitleCollectionViewCell.identifier)
return collectionView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
}
required init?(coder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.frame = contentView.bounds
}
public func configure(with titles: [Title]) {
self.titles = titles
DispatchQueue.main.async { [weak self] in
self?.collectionView.reloadData()
}
}
I extend this class CollectionViewTableViewCell as follows:
extension CollectionViewTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TitleCollectionViewCell.identifier, for: indexPath) as? TitleCollectionViewCell else {
return UICollectionViewCell()
}
//-- get the specific data from the pass-in Data
guard let model = titles[indexPath.row].poster_path else {
return UICollectionViewCell()
}
//- pass data to TitleCollectionViewCell
cell.configure(with: model)
return cell
}
In my Viewcontroller:
I have UItableView to retrieve the cell data as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CollectionViewTableViewCell.identifier, for: indexPath) as? CollectionViewTableViewCell else {
return UITableViewCell()
}
cell.delegate = self
Base on the Problem: for UItableView,there is no such dequeue method: dequeueReusableCellWithReuseIdentifier.
How to solve this problem?
Thanks. Please kindly help me.

Change label text of tableView on button click Swift

I made a simple app for practicing tableView and Singletons. I am new to swift so I would appreciate some quick help on this one. The thing I want to do is simple but I can't think of anything at the moment.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var tableView = UITableView()
let names = Singleton.shared.nameArray
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = .init(x: 0, y: 0, width: 414, height: 260)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let name = names[indexPath.row]
cell.textLabel?.text = "cell \(indexPath.row + 1)"
return cell
}
#IBAction func didPressButton(_ sender: Any) {
}
}
basically I want the tableView label to change to this name constant i made after the button is clicked.
Sorry if I didn't make everything clear in the question and I hope that you will understand and be able to help me. It's kinda hard to explain the code because I'm new to swift and English isn't my native language.
Edited to clear things up a little bit.
Add to the ViewController:
private var areNamesVisible = false
#IBAction func didPressButton(_ sender: Any) {
areNamesVisible.toggle()
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let name = names[indexPath.row]
cell.textLabel?.text = areNamesVisible ? name : "cell \(indexPath.row + 1)"
return cell
}

How to set tableview style to have subtitle programmatically while having dequeueReusableCell?

I want my tableView to have subtitle as well as being able to dequeue properly. I have referred to this link but it does not work for my code. What should I do?
My code is currently like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
//Calling tableview for a reusable cell here will always return a cell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = developerArray[indexPath.row].developerName
cell.detailTextLabel?.text = developerArray[indexPath.row].developerHP
return cell
}
Swift 5
//Declare the variable cell Identifier
let reuseCellIdentifier = “cellIdentifier”;
//Implementation of cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: reuseCellIdentifier)
if (!(cell != nil)) {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: reuseCellIdentifier)
}
cell?.textLabel?.text = //Title text
cell?.detailTextLabel?.text = //Subtitle text
return cell!
}
You can create custom cell using nib by adding labels
For creating custom cell refer this link:
Custom UITableViewCell from nib in Swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.title.text = yourTitleArray[indexPath.row]
cell.detailLbl.text = yourDetailArray[indexPath.row]
return cell
}
So first of all, did you create your table using storyboard or code?
Either way you need to make sure you set the datasource and delegate to self, provided the class they are in conforms to :
UITableViewDataSource
and
UITableViewDelegate
myTable?.delegate = self
myTable?.dataSource = self
Also make sure you register your cell
myTable?.register(myCell.self, forCellReuseIdentifier: "myCell")
And when declaring your cell, you need to force it as the type
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! myCell
Below is a working sample of tableview with a created cell. I hope this helps.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// Create the tableview object
var myTable:UITableView?
override func viewDidLoad() {
// Set the size and location of the tableview
myTable = UITableView(frame: CGRect(x: 0, y: 0, width: 300, height: 600))
// register your cell
myTable?.register(myCell.self, forCellReuseIdentifier: "myCell")
// set the background color of the table, note this wont make a difference unless the cell background is changed.
myTable?.backgroundColor = UIColor.clear
// set the datasource and delegate to self
myTable?.delegate = self
myTable?.dataSource = self
// This is jsut for style, wether there should be seperators or not, and if the user can select multiple lines
myTable?.separatorStyle = .none
myTable?.allowsMultipleSelection = true
// Add the table to your view
self.view.addSubview(myTable!)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// This is declaring how many rows you want in your table. I have 1 but you can do it according to the size of your array.
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create the cell
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! myCell
// set the title text for this cell
cell.title.text = "HelloWorld"
// return the cell
return cell
}
}
and this is the class for the cell we referenced above.
class myCell: UITableViewCell {
var title = UILabel()
var detail = UILabel()
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
title.font = UIFont.boldSystemFont(ofSize: 16)
title.textAlignment = .center
self.contentView.addSubview(title)
self.contentView.addSubview(detail)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
title.frame = CGRect(x: self.contentView.frame.width / 2 - 200, y: 6, width: 400, height: 20)
detail.frame = CGRect(x: 150, y: 10, width: 280, height: 20)
}
}
Let me know if this helps. if you are doing it from story board let me know and I'll adjust.

Accessibility in Swift: Voice Over in TableView returns empty list

I am creating a table view programmatically using custom cell.However when I test using VoiceOver Im getting "empty list" and "content is empty" even though there is value in the cell.Any suggestions?
My tableview -
func createTable(_ choicesArray:[String]){
myTableView = UITableView(frame: CGRect(x: 10, y: self.verticalPostion, width: 340, height: 300))
self.choicesData = choicesArray
myTableView.estimatedRowHeight = 70
myTableView.rowHeight = UITableViewAutomaticDimension
myTableView.allowsMultipleSelection = true
myTableView.register(AnswerCell.self, forCellReuseIdentifier: "cell")
myTableView.dataSource = self
myTableView.delegate = self
myTableView.isAccessibilityElement = false
myTableView.shouldGroupAccessibilityChildren = true
// myTableView.reloadData()
self.view.addSubview(myTableView)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
//more code for cell selection
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return choicesData.count
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, tableView);
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AnswerCell
cell.choiceLabel.text = self.choicesData[indexPath.row]
return cell
}
Code for Custom Cell -
class AnswerCell: UITableViewCell {
var choiceLabel = UILabel()
func configure(_ description: String) {
choiceLabel.text = description
applyAccessibility()
}
final func applyAccessibility() {
isAccessibilityElement = true
accessibilityLabel = choiceLabel.text!
accessibilityHint = "Double tap to play."
choiceLabel.isAccessibilityElement = false;
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(choiceLabel)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
choiceLabel.frame = CGRect(x: 20, y: 0, width: 70, height: 30)
}
}

TableView inside specific UICollectionViewCell programmatically?

i am trying to add a TableView inside UICollectionViewCell, so i want to manage that cell by myself from TableView.
So to be more clear, if indexPath.row is 2, then i want to call my tableView cell's inside collectionview indexPath.row.
Please check the picture, i have made with red colour what i want to do.
I created everything programmatically, using UICollectionViewController and UICollectionViewControllerFlowLayout.
For those who need it, i found the solution:
class CustomizedCell: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate {
var tableView = UITableView()
let cellIdentifier: String = "tableCell"
override func layoutSubviews() {
super.layoutSubviews()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: cellIdentifier)
cell.textLabel?.text = "1 CUP"
cell.detailTextLabel?.text = "Whole"
return cell
}
}
Then at viewDidLoad method at CollectionView i did this:
collectionView?.register(CustomizedCell.self, forCellWithReuseIdentifier: "cell")
And after that i have called like this at cellForRowAt indexPath method of UICollectionView:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomizedCell
return cell
}
You can create a custom UICollectionView cell for that indexpath. And add a tableview in the cell xib.
Implement the delegate methods of tableview in custom cell class.
class CustomCollectionViewCell: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate
{
#IBOutlet var tableView: UITableView!
override func layoutSubviews()
{
super.layoutSubviews()
tableView.delegate = self
tableView.dataSource = self
}
}
The approaches above don't work well in with the MVC pattern. I recommend creating an NSObject subclass which conforms to UITableViewDelegate and UITableViewDataSource.
For example:
class DataProvider: NSObject, UITableViewDelegate, UITableViewDataSource {
let dataManager = DataManager()
let reuseId = "Cell"
//MARK: - DataSource Methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataManager.data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath)
cell.backgroundColor = .yellow
return cell
}
//MARK: - Delegate Methods
}
Now in your UICollectionViewCell subclass you can specify the tableView data source and delegate properties, like so:
class ContainerCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var collectionView: UICollectionView!
let dataProvider = DataProvider()
let tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
override func awakeFromNib() {
super.awakeFromNib()
addSubview(tableView)
tableView.fillSuperview()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: dataProvider.reuseId)
tableView.delegate = dataProvider
tableView.dataSource = dataProvider
}
}
Perhaps not perfect, but achieves better encapsulation than the above answers.
I found a solution that's working for me.
import UIKit
class FirstCollectionViewCell: UICollectionViewCell {
let cellId = "cellId"
let tableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blue
addSubview(tableView)
setUpViews()
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":tableView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":tableView]))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init is not done")
}
func setUpViews() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(MyCell.self, forCellReuseIdentifier: cellId)
}
}
extension FirstCollectionViewCell: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
cell.label.text = "Testing testing"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
}
class MyCell:UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// creating a label to display some dummy text
let label:UILabel = {
let label = UILabel()
label.text = "test"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setUpViews() {
addSubview(label)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":label]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":label]))
}
}

Resources