Swift - Fatal error: unexpectedly found nil while unwrapping an Optional values - ios

I am relatively new to coding with Swift and working with the Xcode IDE. I am trying to implement a new Table View Controller into the storyboard. I have done everything I know how to do this, but I get the
Fatal error: unexpectedly found nil while unwrapping an Optional values
when I click through to the screen in the simulator.
import UIKit
class DrivingTipsTableViewController: UITableViewController
{
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) ->Int
{
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("DrivingLesson") as UITableViewCell
let label = cell.viewWithTag(69) as UILabel
if indexPath.row == 0
{
label.text = "Setup & ball position"
}
else if indexPath.row == 1
{
label.text = "Driving lesson 2"
}
else if indexPath.row == 2
{
label.text = "Driving lesson 3"
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
It shows the breakdown at the let label = line. Looking through some answers on this site, it may be the dequeueReusableCellWithIdentifier("DrivingLesson") that needs changing, but it corresponds with the identifier I gave my cell in the IDE, so I really have no idea where I am going wrong.

First, you should use this method in your first line of your cell for index path:
let cell = tableView.dequeueReusableCellWithIdentifier("DrivingLesson", forIndexPath: indexPath) as UITableViewCell
Then delete let label = cell.viewWithTag(69) as UILabel and just set UITableViewCell's textLabel property:
cell.textLabel.text = "Some text"

Related

How to add a "Load More" cell that when pressed adds more rows to the tableview?

I'm trying to create a tableview only initially shows 5 cells plus a "load more" cell when is pressed more cells will be presented. I'm downloading JSON data and splitting it into 3 arrays, one for the total data, one for data for only the first 5 cells and one for the remaining data after the first 5 cells' data is subtracted from the total data.
I saw this question on here: Load More Cells and tried implementing what they did but with little success. This is what we have so far -
pretty simple, straightforward storyboard with 2 different cells, the first one is the "test cell" which contains the JSON data, the second one is the "expand cell" that when pressed is supposed to show the rest of the cells. Here is my code so far, I omitted a lot of it to keep this post short, I'm currently getting an error it's marked:
var castArray: [CastData?]? = []
var first5CastArray: [CastData?]? = []
var remainderCastArray: [CastData?]? = []
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let firstArray = first5CastArray {
return firstArray.count + 1
} else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < (first5CastArray?.count)! {
let testCell = tableView.dequeueReusableCell(withIdentifier: "testCell", for: indexPath) as! TestCell
testCell.castImageView.image = first5CastImageArray?[indexPath.row]
return testCell
} else {
let expandCell = tableView.dequeueReusableCell(withIdentifier: "expandCell", for: indexPath) as! ExpandCell
expandCell.moreLabel.text = "More"//Error here -fatal error: unexpectedly found nil while unwrapping an Optional value
return expandCell
}
}
//Below code never tested because of fatal error from cellForRowAt indexPath!!!!!
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let remainderArray = remainderCastArray {
for info in remainderArray {
first5CastArray?.append(info)
}
}
expandCell.isHidden = true
tableview.reload
}
I'm not completely sure if I'm going about this the right way, any help is appreciated!
Go back to your storyboard, select that cell again, go to "Attributes Inspector" tab, type in expandCell in identifier field.

load tableview inside uitableviewcell swift

Hi i'm trying to add UITableview inside a UITableViewCell. I had connected the outer tableview cell to the view controller and the inner tableview(inside the cell) to the custom cell class and do the following code
//cellforrow of the outer tableview:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("HistoryCell")! as! OrderHistoryTableViewCell
print(ordersArray[indexPath.row].valueForKey("items"))
cell.lbl_orderStatus.text = ordersArray[indexPath.row].valueForKey("status") as? String
cell.lbl_time.text = ordersArray[indexPath.row].valueForKey("timestamp") as? String
let vc = OrderHistoryTableViewCell() // Custom cell class
vc.tableview_reload(ordersArray[indexPath.row]as! NSMutableDictionary) //pass value to the tableview inside the cell
return cell
}
//code in the custom cell class
func tableview_reload(dict : NSMutableDictionary){
orderItemsarray.removeAllObjects()
let itemsDict = dict.valueForKey("items") as! NSMutableDictionary
for (_,value) in itemsDict
{
let tempDict = value as! NSMutableDictionary
orderItemsarray.addObject(tempDict)
}
self.tbl_Items.reloadData() // fatal error: unexpectedly found nil while unwrapping an optional value
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.orderItemsarray.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell : UITableViewCell!
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "cell");
cell.textLabel?.text = "test"
return cell
}
The data is passed and the func tableview_reload in the custom cell class is called. But when i try to reload the tableview in the custom cell class fatal error:
unexpectedly found nil while unwrapping a optional value occurs.
I had checked the outlet connection and it is connected to the custom cell class. Please advice
Instead of creating new instance using OrderHistoryTableViewCell() you need to use reused cell that you have created using dequeueReusableCellWithIdentifier, so remove line let vc = OrderHistoryTableViewCell() and call tableview_reload method on cell.
cell.tableview_reload(ordersArray[indexPath.row]as! NSMutableDictionary)
return cell

fatal error: unexpectedly found nil while unwrapping an Optional value swift When Selecting tableView Cells

My tableView displays list of cards
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = cardsTable.dequeueReusableCellWithIdentifier("SelectableCardCell", forIndexPath: indexPath) as! CardSelectionTableViewCell
let cardCell: Card!
if self.delegate is CardsApprovalsViewController {
cardCell = approvalCards[indexPath.row] // approvalCard array of type card
} else {
cardCell = fetchedResultsController.objectAtIndexPath(indexPath) as! Card
}
cell.policyHolderName.text = cardCell.policy_holder_name
cell.alphaMark.text = cardCell.policy_holder_name.substringToIndex(advance(cardCell.policy_holder_name.startIndex, 1)).uppercaseString
var image: NSData = cardCell.photo as NSData
cell.picture.highlightedImage = UIImage(data: image)
cell.cardNumber.text = cardCell.member_id
cell.policyNumberLabel.text = cardCell.policy_no
cell.insuranceNameLabel.text = cardCell.insurance.company_name
return cell
}
Afetr selecting one cell, i want cell.alphaMark label to be hidden in order to display cell.picture.highlightedImage
and if the user select another cell, cell.alphaMark for the previous cell should appear again
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell: CardSelectionTableViewCell = cardsTable.cellForRowAtIndexPath(indexPath) as! CardSelectionTableViewCell
selectedCell.alphaMark.hidden = true
let newSwiftColor = UIColor(red: 224, green: 227, blue: 224)
selectedCell.contentView.backgroundColor = newSwiftColor
if self.delegate is CardsApprovalsViewController {
self.card = approvalCards[indexPath.row]
} else {
self.card = fetchedResultsController.objectAtIndexPath(indexPath) as! Card
}
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell = cardsTable.cellForRowAtIndexPath(indexPath) as! CardSelectionTableViewCell // Some times crashes here
selectedCell.alphaMark.hidden = false
}
When i select cell and the select another cell ...etc (Selecting cells repeatedly) alphaMark and picture.highlightedImage works fine, but then it stops and gives me "unexpectedly found nil while unwrapping an Optional value" error
I checked the outlets of the cell and they are connected to the storyboard
Can someone help me with this error ?
Thanks
The easiest thing to do to prevent the crashing, is to change from forced unwrapping of the optional and use an if let instead:
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
if let selectedCell = cardsTable.cellForRowAtIndexPath(indexPath) as? CardSelectionTableViewCell {
selectedCell.alphaMark.hidden = false
}
}
This will take into account that cardsTable.cellForRowAtIndexPath(indexPath) may return nil, so trying to unwrap it into CardSelectionTableViewCell won't fail (because you can't unwrap nil into a CardSelectionTableViewCell)
That will prevent the crashing, but it won't entirely accomplish what you are trying to do. You'll also need to update cellForRowAtIndexPath to check if the current cell is selected and update alphaMark.hidden appropriately.
So one quick crack at how to do that would be to add this to the end of cellForRowAtIndexPath so that each time the cell is brought on screen, it's alphaMark is conditionally hidden based on the cell being selected:
cell.alphaMark.hidden = cell.selected

Swift - TableView crashing when scrolling back up

I have been struggling with this issue. I can scroll freely between the tag cells because it actually remembers them. But if I get the description cell out of my view it immediately removes it from memory and doesn't get it back. Instead I just get "fatal error: unexpectedly found nil while unwrapping an Optional value" when I scroll back to the description. So I have the following pieces of code:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0
tableView.reloadData()
}
I don't know if the viewWillAppear is of any importance in this case but if it is then tell me. Anyway, this is for filling in the cells in my table view:
func GetDescription(cell:descCell, indexPath: NSIndexPath) {
cell.descText.text = descriptTextTwo.htmlToString
}
func GetTagCell(cell:basicTag, indexPath: NSIndexPath) {
let item = tagResults[indexPath.row]!
cell.titleLabel.text = item["tagname"]?.htmlToString
}
func GetValueCell(cell: basicTag, indexPath: NSIndexPath) {
let item = tagResults[indexPath.row]!
cell.valueLabel.text = item["value"]?.htmlToString
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if filledDescription == false {
return getDescriptionAtIndexPath(indexPath)
} else {
return getTagAtIndexPath(indexPath)
}
}
func getDescriptionAtIndexPath(indexPath:NSIndexPath) -> descCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier(descriptionCell) as descCell
GetDescription(cell, indexPath: indexPath)
filledDescription = true
return cell
}
func getTagAtIndexPath(indexPath: NSIndexPath) -> basicTag {
let cell = self.tableView.dequeueReusableCellWithIdentifier(tagCell) as basicTag
GetTagCell(cell, indexPath: indexPath)
GetValueCell(cell, indexPath: indexPath)
return cell
}
So how can I make Swift remember what is in the first cell? Because I am guessing that that is what happens, that it removes what was in the first cell as soon as you get it out of the view. I am guessing I have to do something with "indexPath" but I am not exactly sure how to implement it in this case and if I am far off, please tell me what I am doing wrong. Thanks!
Change the following :
if filledDescription == false {
return getDescriptionAtIndexPath(indexPath)
} else {
return getTagAtIndexPath(indexPath)
}
With:
if indexPath.row == 0 {
return getDescriptionAtIndexPath(indexPath)
} else {
return getTagAtIndexPath(indexPath)
}
This will make sure that the first cell in the table will always treated as a "Description" cell. Since the filledDescription never becomes false after your set it to true, when you get back to the first cell it is treated as a "Tag" cell (due to the if line) where in fact the reusable cell contains "Description" cell data

Expand cell when tapped in Swift

I have been trying to implement a feature in my app so that when a user taps a cell in my table view, the cell expands downwards to reveal notes. I have found plenty of examples of this in Objective-C but I am yet to find any for Swift.
This example seems perfect: Accordion table cell - How to dynamically expand/contract uitableviewcell?
I had an attempt at translating it to Swift:
var selectedRowIndex = NSIndexPath()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRowIndex = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedRowIndex == selectedRowIndex.row && indexPath.row == selectedRowIndex.row {
return 100
}
return 70
}
However this just seems to crash the app.
Any ideas?
Edit:
Here is my cellForRowAtIndexPath code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
if tableView == self.searchDisplayController?.searchResultsTableView {
cell.paymentNameLabel.text = (searchResults.objectAtIndex(indexPath.row)) as? String
//println(searchResults.objectAtIndex(indexPath.row))
var indexValue = names.indexOfObject(searchResults.objectAtIndex(indexPath.row))
cell.costLabel.text = (values.objectAtIndex(indexValue)) as? String
cell.dateLabel.text = (dates.objectAtIndex(indexValue)) as? String
if images.objectAtIndex(indexValue) as NSObject == 0 {
cell.paymentArrowImage.hidden = false
cell.creditArrowImage.hidden = true
} else if images.objectAtIndex(indexValue) as NSObject == 1 {
cell.creditArrowImage.hidden = false
cell.paymentArrowImage.hidden = true
}
} else {
cell.paymentNameLabel.text = (names.objectAtIndex(indexPath.row)) as? String
cell.costLabel.text = (values.objectAtIndex(indexPath.row)) as? String
cell.dateLabel.text = (dates.objectAtIndex(indexPath.row)) as? String
if images.objectAtIndex(indexPath.row) as NSObject == 0 {
cell.paymentArrowImage.hidden = false
cell.creditArrowImage.hidden = true
} else if images.objectAtIndex(indexPath.row) as NSObject == 1 {
cell.creditArrowImage.hidden = false
cell.paymentArrowImage.hidden = true
}
}
return cell
}
Here are the outlet settings:
It took me quite a lot of hours to get this to work. Below is how I solved it.
PS: the problem with #rdelmar's code is that he assumes you only have one section in your table, so he's only comparing the indexPath.row. If you have more than one section (or if you want to already account for expanding the code later) you should compare the whole index, like so:
1) You need a variable to tell which row is selected. I see you already did that, but you'll need to return the variable to a consistent "nothing selected" state (for when the user closes all cells). I believe the best way to do this is via an optional:
var selectedIndexPath: NSIndexPath? = nil
2) You need to identify when the user selects a cell. didSelectRowAtIndexPath is the obvious choice. You need to account for three possible outcomes:
the user is tapping on a cell and another cell is expanded
the user is tapping on a cell and no cell is expanded
the user is tapping on a cell that is already expanded
For each case we check if the selectedIndexPath is equal to nil (no cell expanded), equal to the indexPath of the tapped row (same cell already expanded) or different from the indexPath (another cell is expanded). We adjust the selectedIndexPath accordingly. This variable will be used to check the right rowHeight for each row. You mentioned in comments that didSelectRowAtIndexPath "didn't seem to be called". Are you using a println() and checking the console to see if it was called? I included one in the code below.
PS: this doesn't work using tableView.rowHeight because, apparently, rowHeight is checked only once by Swift before updating ALL rows in the tableView.
Last but not least, I use reloadRowsAtIndexPath to reload only the needed rows. But, also, because I know it will redraw the table, relayout when necessary and even animate the changes. Note the [indexPath] is between brackets because this method asks for an Array of NSIndexPath:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("didSelectRowAtIndexPath was called")
var cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCustomTableViewCell
switch selectedIndexPath {
case nil:
selectedIndexPath = indexPath
default:
if selectedIndexPath! == indexPath {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
}
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
3) Third and final step, Swift needs to know when to pass each value to the cell height. We do a similar check here, with if/else. I know you can made the code much shorter, but I'm typing everything out so other people can understand it easily, too:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let smallHeight: CGFloat = 70.0
let expandedHeight: CGFloat = 100.0
let ip = indexPath
if selectedIndexPath != nil {
if ip == selectedIndexPath! {
return expandedHeight
} else {
return smallHeight
}
} else {
return smallHeight
}
}
Now, some notes on your code which might be the cause of your problems, if the above doesn't solve it:
var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell
I don't know if that's the problem, but self shouldn't be necessary, since you're probably putting this code in your (Custom)TableViewController. Also, instead of specifying your variable type, you can trust Swift's inference if you correctly force-cast the cell from the dequeue. That force casting is the as! in the code below:
var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier" forIndexPath: indexPath) as! CustomTransactionTableViewCell
However, you ABSOLUTELY need to set that identifier. Go to your storyboard, select the tableView that has the cell you need, for the subclass of TableViewCell you need (probably CustomTransactionTableViewCell, in your case). Now select the cell in the TableView (check that you selected the right element. It's best to open the document outline via Editor > Show Document Outline). With the cell selected, go to the Attributes Inspector on the right and type in the Identifier name.
You can also try commenting out the cell.selectionStyle = UITableViewCellSelectionStyle.None to check if that's blocking the selection in any way (this way the cells will change color when tapped if they become selected).
Good Luck, mate.
The first comparison in your if statement can never be true because you're comparing an indexPath to an integer. You should also initialize the selectedRowIndex variable with a row value that can't be in the table, like -1, so nothing will be expanded when the table first loads.
var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex.row {
return 100
}
return 70
}
Swift 4.2 var selectedRowIndex: NSIndexPath = NSIndexPath(row: -1, section: 0)
I suggest solving this with modyfing height layout constraint
class ExpandableCell: UITableViewCell {
#IBOutlet weak var img: UIImageView!
#IBOutlet weak var imgHeightConstraint: NSLayoutConstraint!
var isExpanded:Bool = false
{
didSet
{
if !isExpanded {
self.imgHeightConstraint.constant = 0.0
} else {
self.imgHeightConstraint.constant = 128.0
}
}
}
}
Then, inside ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.estimatedRowHeight = 2.0
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.tableFooterView = UIView()
}
// TableView DataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ExpandableCell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as! ExpandableCell
cell.img.image = UIImage(named: indexPath.row.description)
cell.isExpanded = false
return cell
}
// TableView Delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
UIView.animate(withDuration: 0.3, animations: {
tableView.beginUpdates()
cell.isExpanded = !cell.isExpanded
tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
tableView.endUpdates()
})
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
UIView.animate(withDuration: 0.3, animations: {
tableView.beginUpdates()
cell.isExpanded = false
tableView.endUpdates()
})
}
}
Full tutorial available here
A different approach would be to push a new view controller within the navigation stack and use the transition for the expanding effect. The benefits would be SoC (separation of concerns). Example Swift 2.0 projects for both patterns.
https://github.com/justinmfischer/SwiftyExpandingCells
https://github.com/justinmfischer/SwiftyAccordionCells
After getting the index path in didSelectRowAtIndexPath just reload the cell with following method
reloadCellsAtIndexpath
and in heightForRowAtIndexPathMethod check following condition
if selectedIndexPath != nil && selectedIndexPath == indexPath {
return yourExpandedCellHieght
}

Resources