SIGABRT error in dequeueReusableCell(withIdentifier:), when using custom UITableViewCell - ios

I'm building an app with multiple scenes and a table view with custom cells in each. I got the home screen table view to work fine and then I segue to the new scene from the custom cells. When it segues, my second view controller crashes.
Here is my code for the view controller
import UIKit
class QuestionViewController: UIViewController {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var submitButton: UIButton!
#IBOutlet weak var qTableView: UITableView!
var answers : [QuestionOption] = []
override func viewDidLoad() {
super.viewDidLoad()
answers = [QuestionOption(text: "test"), QuestionOption(text: "test"), QuestionOption(text: "test"), QuestionOption(text: "test")]
qTableView.delegate = self
qTableView.dataSource = self
submitButton.setTitle("Submit", for: .normal)
questionLabel.text = "test question"
}
}
extension QuestionViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return answers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let a = answers[indexPath.row]
let cell = qTableView.dequeueReusableCell(withIdentifier: "QuestionOptionCell") as! QuestionOptionCell
cell.setOption(option: a)
return cell
}
}
Here's my code for the cell
import UIKit
class QuestionOptionCell: UITableViewCell {
#IBOutlet weak var cellTitle: UILabel!
func setOption(option: QuestionOption){
cellTitle.text = option.text
}
}
Here's my code for the QuestionOption class
import Foundation
import UIKit
class QuestionOption{
var text: String
init(text: String){
self.text = text
}
}
Crash log
2019-02-20 14:33:28.394695-0800 iQuiz[8935:822409] *** NSForwarding: warning: object 0x7fd608407c40 of class 'iQuiz.QuestionOption' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[iQuiz.QuestionOption initWithCoder:]
2019-02-20 14:33:28.395281-0800 iQuiz[8935:822409] Unrecognized selector -[iQuiz.QuestionOption initWithCoder:]
Here's my storyboard if that helps at all
I've made sure my identifier matches and I don't have any extraneous or unconnected outlets, those are the only solution to this problem I can find online.

The crash log says that QuestionOption must be a subclass of NSObject and adopt NSCoding which is overkill in this case. Actually a struct would be sufficient.
You can avoid it by deleting the method in QuestionOptionCell
func setOption(option: QuestionOption){
cellTitle.text = option.text
}
and set the value in cellForRowAt directly by replacing
cell.setOption(option: a)
with
cell.cellTitle.text = a.text

Things to check:
Verify that "QuestionOptionCell" is indeed the reuse identifier for the cell.
Verify that the selected type for the cell is QuestionOptionCell.
In cellForRowAt, use tableView.dequeueReusableCell instead of qTableView.dequeueReusableCell.
Otherwise, share the crash log with us.

Related

Use delegate to show/hide a tableview cell

I have a tableview with three rows, In the second row there is UISwitch that when it's off, the third row should be hidden and when it's on, it would be shown.
I use delegate for that:
here is the protocol:
protocol ChangeStatusOFSwitchBtnDelegate {
func toggle(isOn: Bool)
}
and here is the configuration of the delegate in UITableViewCell
#IBOutlet weak var deadlineSwitchState: UISwitch!
func configure(swicthIsOn: Bool, delegate: ChangeStatusOFSwitchBtnDelegate) {
deadlineSwitchState.isOn = swicthIsOn
self.switchBtnDelegate = delegate
}
#IBAction func changeStateToggle(_ sender: UISwitch) {
if deadlineSwitchState.isOn == true {
switchBtnDelegate!.toggle(isOn:true)
} else {
switchBtnDelegate!.toggle(isOn:false)
}
}
var switchBtnDelegate: ChangeStatusOFSwitchBtnDelegate?
and here is some part of the codes in UIViewController
class AddListPopup: UIViewController, UITableViewDelegate, UITableViewDataSource, ChangeStatusOFSwitchBtnDelegate {
// Delegate
private var switchBtnIsOn = false
func toggle(isOn: Bool) {
self.switchBtnIsOn = isOn
NewListDetailsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (taskCellArray.count - 1 ) + (switchBtnIsOn ? 1 : 0)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
indexPath.row == 1 {
let switchCell = tableView.dequeueReusableCell(withIdentifier: "switch cell", for: indexPath) as! NewListPopupViewCell
switchCell.configure(swicthIsOn: switchBtnIsOn, delegate: self) //Set delegate here
return switchCell
}
return UITableViewCell()
}
the problem is:
I will get two errors here:
first, if I unwrap switchBtnDelegate in UITableViewCell same as the code you see here, I got:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
this error appears here:
#IBOutlet weak var deadlineSwitchState: UISwitch!
#IBAction func changeStateToggle(_ sender: UISwitch) {
if deadlineSwitchState.isOn == true {
switchBtnDelegate!.toggle(isOn:true) //The error appears here
} else {
switchBtnDelegate!.toggle(isOn:false)
}
}
var switchBtnDelegate: ChangeStatusOFSwitchBtnDelegate?
and If i use ? to keep it optional:
#IBOutlet weak var deadlineSwitchState: UISwitch!
#IBAction func changeStateToggle(_ sender: UISwitch) {
if deadlineSwitchState.isOn == true {
switchBtnDelegate?.toggle(isOn:true)
} else {
switchBtnDelegate?.toggle(isOn:false)
}
}
var switchBtnDelegate: ChangeStatusOFSwitchBtnDelegate?
I got this error:
class AppDelegate: UIResponder, UIApplicationDelegate { Thread 1: signal SIGABRT
Could anyone help me to where the problem comes from?
Thank you so much
(1)
The reason that your delegate is not being set appears to be that you are missing an if in your code on the first line of tableView(UITableView, cellForRowAt:IndexPath). It looks like that code in the block that you had intended to be an if statement is never running.
I would add an if on that first line and see if it is doing what you wanted.
Update: This would also explain the message unrecognized selector sent to instance 0x7fd3a4068800' in the console, because your method would always return a UITableViewCell, instead of a NewListPopupViewCell.
(2)
Crashing like this
class AppDelegate: UIResponder, UIApplicationDelegate { Thread 1: signal SIGABRT
usually happens to me when:
1. I'm trying to load a Storyboard that is not part of the target, or
2. I'm trying to use a segue that is not on the controller I'm messaging, or
3. There are missing connections between the controller and other elements in the storyboard.
So, I would make sure that everything in your storyboard (or xib) has the appropriate connections, and try again.
(3)
Regarding the delegate ... You should make sure that your delegates are always declared using the weak keyword, and they are always treated as optional.
weak var delegate: ChangeStatusOFSwitchBtnDelegate?

Looping through phone numbers, creating a custom view and passing it the phone number in swift

I'm having trouble creating a view programatically inside a for loop from another controller. The parent controller is a tableviewcell and I'm looping through a bunch of phone numbers inside a CNContact object. For each phone number the contact has I wish to create my custom view and add it to the tableviewcell and have it stack vertically.
So far I managed to create the view and add it to the tableviewcell but wasn't able to pass the data. It's the passing of the data from one controller to another that I'm struggling with.
Here is my code:
ContactListTableViewCell.swift
import UIKit
import Contacts
class ContactListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var phonenumberView: UIView!
func configureCell(contact: CNContact) {
titleLabel.text = "\(contact.givenName) \(contact.familyName)"
for phoneNumber in contact.phoneNumbers {
let view = self.createContactListTableViewTelephoneRow(telephone: phoneNumber)
self.phonenumberView.addSubview(view)
}
}
func createContactListTableViewTelephoneRow(telephone: Any) -> UIView {
let controller = ContactListTableViewTelephoneRow()
let view = UINib(nibName: "ContactListTableViewTelephoneRow", bundle: nil).instantiate(withOwner: controller, options: nil)[0] as! UIView
return view
}
}
contactListTableViewCell prototype inside Main.storyboard
ContactListTableViewTelephoneRow.swift
class ContactListTableViewTelephoneRow: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var telephoneLabel: UILabel!
#IBOutlet weak var telephoneTypeLabel: UILabel!
func setData(telephoneLabelText: String, telephoneTypeLabelText: String) {
telephoneLabel?.text = telephoneLabelText
telephoneTypeLabel?.text = telephoneTypeLabelText
}
}
ContactListTableViewTelephoneRow.xib
Any help would be much appreciated. Thank you.
Simple way to pass data is you need to crate object in your second controller and pass data from first controller
let vc = self.storyboard!.instantiateViewController(withIdentifier: "Secondcontroller") as! Secondcontroller
vc.yourObject = object //To pass
self.present(tabvc, animated: true, completion: nil) // or push
You will need to cast the view you create using UNib.[...] and pass the data directly to it:
func createContactListTableViewTelephoneRow(telephone: CNLabeledValue<CNPhoneNumber>) -> UIView {
let nib = UINib(nibName: "ContactListTableViewTelephoneRow", bundle: nil)
let root = nib.instantiate(withOwner: nil, options: nil)[0]
let view = root as! ContactListTableViewTelephoneRow
view.setData(telephoneLabelText: telephone.value.stringValue,
telephoneTypeLabelText: telephone.label!) // make sure `telephone.label!` is correct – I never compiled it
return view
}
Note that I adjusted the signature of createContactListTableViewTelephoneRow(telephone:).
But as an advise overall: I would solve your UI problem in a very different way.
Background: UITableViews heavily reuses (queues/dequeues) cells so that scroll performance is acceptable. Although I assume you use the APIs of UITableViewDataSource correctly loading nibs inside the your cells can become a performance bottleneck very fast.
I would advise against having variable number of ContactListTableViewTelephoneRow in your cell. Instead make it a subclass of UITableViewCell as well. Your view controller of course must handle at least two different types of cells in this case. You can use different sections to still keep the logic fairly easy. Here is a full example: (you would of course need to adjust styling)
import Contacts
import UIKit
class ContactListTelephoneTableViewCell: UITableViewCell {
#IBOutlet weak var telephoneLabel: UILabel!
#IBOutlet weak var telephoneTypeLabel: UILabel!
func configureCell(telephone: CNLabeledValue<CNPhoneNumber>) {
telephoneLabel.text = telephone.value.stringValue
telephoneTypeLabel.text = telephone.label!
}
}
class ContactListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configureCell(contact: CNContact) {
titleLabel.text = "\(contact.givenName) \(contact.familyName)"
}
}
class DataSource: NSObject, UITableViewDataSource {
var contacts: [CNContact]!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts[section].phoneNumbers.count + 1 // one extra for given and family name
}
func numberOfSections(in tableView: UITableView) -> Int {
return contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
return self.tableView(tableView, nameCellForRowAt: indexPath)
} else {
return self.tableView(tableView, phoneCellForRowAt: indexPath)
}
}
func tableView(_ tableView: UITableView, nameCellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "name", for: indexPath) as! ContactListTableViewCell
cell.configureCell(contact: contacts[indexPath.section])
return cell
}
func tableView(_ tableView: UITableView, phoneCellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "phone", for: indexPath) as! ContactListTelephoneTableViewCell
let contact = contacts[indexPath.section]
let telephone = contact.phoneNumbers[indexPath.row - 1] // minus one for given and family name
cell.configureCell(telephone: telephone)
return cell
}
}

How can I call viewDidLoad in a UITableViewCell?

My code:
import Foundation
import Firebase
class CellOneViewController: UITableViewCell {
#IBOutlet weak var new1: UILabel!
let ref = Firebase(url: "https://burning-heat-8250.firebaseio.com/slide2")
func viewdidload() {
ref.observeEventType (.Value, withBlock: { snapshot in
self.new1.text = snapshot.value as? String
})
}
}
I've read around that you can't call viewDidLoad in a UITableViewCell, only in a UITableViewController. All the answers are in Objective-C, but I'm writing the app in Swift. I don't receive any critical errors but when running the app nothing appears in the cell where the label is. I'm fairly new to using Xcode, as I am just going around following guides so if I'm saying something incorrect let me know.
I think, you need method func layoutSubviews().
Only ViewController gets func viewDidLoad() called, after view is loaded.
If you need to initialize something or update views, you need to do in layoutSubviews(). As soon, your view or UITableViewCell gets loaded, layoutSubviews() get called.
Replace viewDidLoad with layoutSubviews()
class CellOneViewController: UITableViewCell {
#IBOutlet weak var new1: UILabel!
let ref = Firebase(url: "https://burning-heat-8250.firebaseio.com/slide2")
override func layoutSubviews() {
super.layoutSubviews()
ref.observeEventType (.Value, withBlock: { snapshot in
self.new1.text = snapshot.value as? String
})
}
}
The reason you can't do this is that a UITableViewCell isn't a subclass of UIViewController.
The cellForRowAtIndexPath method in the UITableViewDataSource is where you should set up your cells. You probably only want to do something like this in your custom UITableViewCell:
class CellOne: UITableViewCell {
#IBOutlet weak var new1: UILabel!
}
Then in your TableViewController's cellForRowAtIndexPath method (provided you have imported firebase) you should dequeue a reusable cell, cast it as a CellOne (as! cellOne) and then you can set the new1.text value. I don't know what your reuse identifier is, but it would look something like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Your-Reuse-Identifier", forIndexPath: indexPath) as! CellOne
cell.new1.text = "Your Value"
return cell
}

Swift custom UITableViewCell label is always nil

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

Accessing custom table cell labels

I have created two custom labels in a table cell in order to be able to dynamically resize the cell(s) to it's content. I first tried using the "Subtitle" style and this worked out great except that the cell(s) didn't resize the way i wanted to and it looked really messy.
My question is: how do I access these labels in order to append my value's from my API to them?
View controller code:
import UIKit
class nyheterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, APIControllerProtocol {
#IBOutlet weak var nyheterTableView: UITableView!
#IBOutlet weak var titleLabel: UILabel!
var searchResultsData: NSArray = []
var api: APIController = APIController()
func JSONAPIResults(results: NSArray) {
dispatch_async(dispatch_get_main_queue(), {
self.searchResultsData = results
print(self.searchResultsData)
self.nyheterTableView.reloadData()
})
}
override func viewDidLoad() {
super.viewDidLoad()
var APIBaseUrl: String = "http://*.se/*/*.php"
var urlString:String = "\(APIBaseUrl)"
//Call the API by using the delegate and passing the API url
self.api.delegate = self
api.GetAPIResultsAsync(urlString, elementName:"news")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//print(self.searchResultsData.count)
return self.searchResultsData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier: String = "nyheterResultsCell"
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as UITableViewCell
//nyheterTableViewCell.cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier);
//Create a variable that will contain the result data array item for each row
var cellData: NSDictionary = self.searchResultsData[indexPath.row] as NSDictionary
//Assign and display the Title field
var releaseDate: String = cellData["date"] as String
var titleVar: String = cellData["title"] as String
var titleMix: String = "\(titleVar)" + " - " + "\(releaseDate)"
cell.textLabel?.text = titleMix //textLabel worked out fine using "Subtitle" style.
// Get the release date string for display in the subtitle
cell.detailTextLabel?.text = cellData["content"] as String? //Same
return cell
}
}
I understand that I can't access these labels without somehow connecting them to the ViewController. Creating outlets to the ViewController generates an error about that I can't use connections from the prototype cell to the ViewController.
So, i created a new class, called nyheterTableViewCell which i connect to the table cell and connected outlets to my labels.
nyhterTableViewCell code:
import UIKit
class nyheterTableViewCell: UITableViewCell {
#IBOutlet weak var nyhetLabel: UILabel!
#IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I'm an beginner at Swift-programming and Xcode.
Cheers!
You don't need the labels connected to the view controller. Your example of the custom table view cell looks correct.
To access the label properties, you're going to want to change the line
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as UITableViewCell
to
let cell: nyheterTableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as nyheterTableViewCell

Resources