Custom cell: fatal error: unexpectedly found nil while unwrapping an Optional value - iphone

I have a table view with custom cell that was created as .xib . I didnt use storyboard. I have a problem that I couldnt fill my table with the data which came from webservice result. Also, I have 4 labels in the custom cell. In my custom cell class, when I try to set labels for each items, It gives me fatal error like above.
Here is my code:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
...
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
let cell: ItemManagementTVCell = tableView?.dequeueReusableCellWithIdentifier("cell") as ItemManagementTVCell
if let ip = indexPath
{
let item: Item = self.itemList[indexPath.row] as Item
cell.setCell(item.itemName, status: item.itemStatus, duration: item.itemDuration, price: item.itemPrice)
}
return cell
}
}
And my custom cell class is here :
import UIKit
class ItemManagementTVCell: UITableViewCell {
#IBOutlet var lblItemName: UILabel!
#IBOutlet var lblItemPrice: UILabel!
#IBOutlet var lblItemDuration: UILabel!
#IBOutlet var lblItemStatus: UILabel!
override func awakeFromNib()
{
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
}
func setCell(name: String, status: Int, duration: Int, price: Int)
{
self.lblItemName.text = name
self.lblItemStatus.text = String(status)
self.lblItemDuration.text = "Duration: \(String(duration)) months"
self.lblItemPrice.text = String(price) + " $"
}
}
I am getting the error inside of "setCell" method block.
I have read a lot of questions and solutions and I tried all of them it doesnt work for me.
Thank you for your answers,
Best regards.
SOLUTION: I've solved this problem by linking the cell items to cell's own instead of linking to File's Owner. My problem has gone by doing this.

Another solution to the problem without having to link cell items to the cell owner:
let nib = UINib(nibName: "YOUR_CUSTOM_CELL_NIB_NAME", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "YOUR_CUSTOM_CELL_ID")

Your "cell" must be nil.
Using
tableView.dequeueReusableCellWithIdentifier("cell") as ItemManagementTVCell
Can return nil. You should use:
tableView.dequeueReusableCellWithIdentifier("cell" forIndexPath:indexPath) as ItemManagementTVCell
This way it guarantees cells is not nil.
EDIT: Maybe you can prevent the crash by putting if statements inside "setCell"
if var itemName = self.lblItemName {
itemName.text = name
}
Do that for every label you set inside it and check if the crash still happens. If it don't you must check why those labels are nil.

Related

How to add additional textfields by clicking button in table view

I am trying to add an option to add additional student fields inside table so that user can add more than one student name.
But I am confused how to do it using table view.
I am not interested in hiding view with specific number of fields.
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
struct listItems{
var title : String
var isExpandable:Bool
var maxFields :Int
init(title:String,isExp:Bool,mxF:Int) {
self.title = title
self.isExpandable = isExp
self.maxFields = mxF
}
}
#IBOutlet weak var tblListTable: UITableView!
let data : [listItems] = [listItems(title: "Name", isExp: false, mxF: 1), listItems(title: "Student Name", isExp: true, mxF: 20), listItems(title: "Email", isExp: false, mxF: 1)]
override func viewDidLoad() {
super.viewDidLoad()
tblListTable.delegate = self
tblListTable.dataSource = self
self.tblListTable.reloadData()
print("isLoaded")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRow")
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ListCell
cell.lblName.text = data[indexPath.row].title
if data[indexPath.row].isExpandable == true {
cell.btnAddField.isHidden = false
print("ishidden")
}
else {
cell.btnAddField.isHidden = true
}
return cell
}
}
List Cell Class
import UIKit
protocol AddFieldDelegate : class {
func addField( _ tag : Int)
}
class ListCell: UITableViewCell {
#IBOutlet weak var btnAddField: UIButton!
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var txtField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func addField( _ tag : Int){
}
}
You are on the right track creating the AddFieldDelegate. However, rather than implementing the method inside the ListCell class you need to implement it in the ViewController.
First, change the view controller class definition line to:
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource, AddFieldDelegate {
This will allow you to call the delegate method from the view controller. Next, when you are creating your table view cells add the line:
cell.delegate = self
After that, move the method definition of the method addField to the view controller.
So inside of your view controller add:
func addField(titleOfTextFieldToAdd: String, numberAssociatedWithTextFieldToAdd: Int) {
data.append(listItems(title: titleOfTextFieldToAdd, isExp: false, mxF: numberAssociatedWithTextFieldToAdd))
self.tableView.reloadData()
}
I used an example definition of the addField method but you can change it to anything that you would like, just make sure that you change the data array and reload the table view data.
Lastly, we must define the delegate in the ListCell class. So add this line to the ListCell class:
weak var delegate: MyCustomCellDelegate?
You can then add the text field by running the following anywhere in your ListCell class:
delegate?.addField(titleOfTextFieldToAdd: "a name", numberAssociatedWithTextFieldToAdd: 50)
For more information on delegation, look at the answer to this question.
You have to append another item in your data array on button click and reload the tableview.

How do I use UIStepper to change a property in a Realm Model Object?

I currently have a ViewController with a TableView inside it called SelectedListItemsViewController. This ViewController's TableView is populated by a custom cell class called SelectedListItemsTableViewCell.
I have an array of Realm Model Objects called selectedListItems, each of which has several properties. The SelectedListItemsTableViewCell populates the TableView with the listItem property of that indexPath.row's object, and each row has a UIStepper with a UILabel next to it that (as of now) shows UIStepper.value for each row. Ideally, the label will reflect the listItemWeight property of each row, and change it when incrementing or decrementing that row.
This is my custom cell:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
}
}
And in my ViewController's cellForRowAtIndexPath, I've configured the cell like so:
// Configure the cell...
cell.selectedListItemLabel.text = selectedListItems[indexPath.row].listItem
cell.listItemWeightLabel.text = "\(selectedListItems[indexPath.row].listItemWeight)"
Which perfectly loads the listItem property, and the listItemWeight property shows up correctly, but as soon as I increment or decrement on the UIStepper it gets messed up.
How do I properly link my UILabel and UIStepper to the [indexPath.row].listItemWeight?
In the same method that gets called when your stepper updates, update your listItem. However, since this item is stored in your Realm database, you will have to get an instance of your Realm database and write the change to the database.
You can do this by having your TableViewCell hold on to an instance of the listItem. Your new TableViewCell class will look something like this:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
var listItem: Item?
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
if let listItem = listItem {
let realm = try! Realm
try! realm.write {
listItem.listItemWeight = Int(sender.value) ?? 0
}
}
}
}
The above answer was helpful in leading me to the actual solution of my issue, but if anyone in the future is curious - I ended up using a closure.
In my custom cell class, I did
#IBAction func stepperValueChanged(sender: UIStepper) {
selectedListItemLabel.text = Int(sender.value).description
tapped?(self)
}
And in my view controller, I did
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! SelectedListItemsTableViewCell
cell.stepperControl.value = selectedListItems[indexPath.row].listItemWeight
// Configure the cell...
cell.tapped = { (selectedCell) -> Void in
selectedListItems[indexPath.row].listItemWeight = cell.stepperControl.value
}
Which allowed me to access each cell's UIStepper in the view controller file.
It helped to read flashadvanced's Option 2 answer in this thread.

How to get cell index from uiview in uitableviewcell (swift/xcode)

I have two UIViewControllers called upVote and downVote in MyTableViewCell class. I have added the gesture recognizers for tapping these views programmatically but am not sure how to get the parent cell or index of the cell so I know which cell the UIView that was tapped is in. How do I get the cell or index of the tapped cell in the functions handleTapUp and handleTapDown?
Here is my class
class MyTableViewCell: UITableViewCell {
// MARK: Properties
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var upVote: UIImageView!
#IBOutlet weak var downVote: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let tapUp = UITapGestureRecognizer(target: self, action: "handleTapUp:")
tapUp.delegate = self
upVote.addGestureRecognizer(tapUp)
let tapDown = UITapGestureRecognizer(target: self, action: "handleTapDown:")
tapDown.delegate = self
downVote.addGestureRecognizer(tapDown)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func handleTapUp(sender: UITapGestureRecognizer? = nil) {
// handling code
// get cell(index)
}
func handleTapDown(sender: UITapGestureRecognizer? = nil) {
// handling code
// get cell()
}
}
I have tried self.superclass to get the uitableviewcell, and when i print the debug description it say uitableviewcell, but whenever I try to do anything else with the object I get an error that self.superclass is type AnyObject. I have tried declaring/casting self.superclass in many different ways and none have worked. This seems like it should be simple so hopefully I am missing something.
Declare a variable in your customCellClass:-
var indexForCell : NSIndexPath!
In your ViewControllers where you are dequeing the cells in the function func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell add this :-
cell.indexForCell = indexPath
Now access the indexForCell in your functions in your class:-
func handleTapUp(sender: UITapGestureRecognizer? = nil) {
let rowValue = indexForCell.row
}
func handleTapDown(sender: UITapGestureRecognizer? = nil) {
// handling code
// get cell()
}

Using the contentView property on a custom tableView cell (being passed as a header) how to prevent it from nullifying the custom attributes?

For example here is my custom cell:
protocol SectionHeaderTableViewCellDelegate {
func didSelectUserHeaderTableViewCell(sectionHeader: SectionHeaderTableViewCell, selected: Bool, type: Type)
}
class SectionHeaderTableViewCell: UITableViewCell {
#IBOutlet weak var labelContainerView: LabelContainerView!
#IBOutlet weak var sectionTitleLabel: UILabel!
#IBOutlet weak var plusButton: UIButton!
var type: Type?
var delegate: SectionHeaderTableViewCellDelegate?
var dog: Dog?
let sections = [Type.Meals, Type.Exercise, Type.Health, Type.Training, Type.Misc]
}
extension SectionHeaderTableViewCell {
#IBAction func plusButtonPressed(sender: AnyObject) {
if let type = type {
delegate?.didSelectUserHeaderTableViewCell(self, selected: plusButton.selected, type: type )
}
}
In my controller if I add a return of header.contenView I get the desired results of the header staying in place but unfortunately it nullifies the button included in the custom header preventing it from being called. Otherwise if I simply just return header the button on the custom header cell works as expected but the header moves with the row being deleted which is obviously unsightly and not what I want.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let header = tableView.dequeueReusableCellWithIdentifier("sectionHeader") as? SectionHeaderTableViewCell else { return UITableViewCell() }
header.delegate = self
header.updateDogWithGender(dog)
header.type = header.sections[section]
header.sectionTitleLabel.text = header.sections[section].rawValue
return header.contentView
}
moving headers
In case anyone runs into a similar situation the solution was to create a Nib file and customize it as you see fit. Create a nib file by going to File -> New File -> iOS -> User Interface -> and selecting View. Create Nib file. I added my views and buttons to get the look I wanted. customize Nib. From there I changed the custom cell class to be UITableViewHeaderFooterView instead and reconnected my outlets and actions to the new Nib file.
class SectionHeaderView: UITableViewHeaderFooterView {... previous code from above }
Back in the controller update the viewForHeaderInSection function to load a nib instead :
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = NSBundle.mainBundle().loadNibNamed("SectionHeader", owner: self, options: nil).first as? SectionHeaderView
header?.delegate = self
header?.updateDogWithGender(dog)
header?.type = header?.sections[section]
header?.sectionTitleLabel.text = header?.sections[section].rawValue
return header
}
By the way we declared the property first at the end of the loadNibNamed property because it returns an array of AnyObjects and since my Nib file only contains one UIView that houses a label and a button I only needed the first and only item in the array. Thanks to my mentor James for figuring this out!

change image in Xcode Swift when row selected

My first Phone app in Xcode (7.3 and Swift 2). Spent weeks trying to change an image when a row is selected.
myImageView is a custom cell class (swift file) with the imageView outlet:
import UIKit
class myTableViewCell: UITableViewCell {
#IBOutlet weak var myCellLabel: UILabel!
#IBOutlet weak var myImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
When a row is selected I want to replace the image but unable to reference the cell.myImageView.image{indexPath.row] = UIImage(named: “SquareGreen.png”)
This is OK
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! myTableViewCell
cell.myImageView.image = UIImage(named: "Square.png")
This does not work
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
myTableViewCell.myImageView.image[indexPath.row] = "SquareGreen.png"
cell.myImageView.image[indexPath.row] = UIImage(named: "SquareGreen.png")
// error “use of unresolved identifier cell”
}
any ideas?
This line is wrong:
myTableViewCell.myImageView.image[indexPath.row] = "SquareGreen.png"
change to:
let cell = tableView.cellForRowAtIndexPath(indexPath) as myTableViewCell
cell.myImageView.image = "SquareGreen.png"
The first problem was what Phillip Mills pointed out in the comments - you don't have a cell variable, you need to create one. The second problem was that to assign a new image to the myImageView property, you need to assign as my code shows above (image is not an array or a dictionary).