2

I have learned Swift and Xcode for about 3 weeks and I am doing an Todo App just for practice. But when I was trying to add new item to my todo list the error occurred saying that "Unexpectedly found nil while unwrapping an Optional value".

I have the main screen which is created by Main.storyboard and an add button on the main screen. When click that button it goes to a new ViewController which I created via the Eureka framework, user can enter some information like title, description, category in that form and pass them back to the main screen. I used the delegate protocol for passing data, when I click the Save Item button the app crashed and it said that

"Fatal error: Unexpectedly found nil while unwrapping an Optional value".

The code is below:

import Foundation
import Eureka
protocol CanReceive {

    func dataReceived(data: ToDo)

}
class AddItemViewController : FormViewController {

    var delegate : CanReceive?

    var todoItem : ToDo?

    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d yyyy, h:mm a"
        return formatter
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        form +++ Section()
            <<< TextRow(){
                $0.title = "Title"
                $0.placeholder = "Enter text here"
                $0.onChange { [unowned self] row in
                    self.todoItem?.title = row.value
                }
            }
            <<< TextRow(){
                $0.title = "Description"
                $0.placeholder = "Give some description"
                $0.onChange { [unowned self] row in
                    self.todoItem?.description = row.value
                }
            }

            <<< AlertRow<String>() {
                $0.title = "Category"
                $0.selectorTitle = "Select the category"

                $0.options = ["Personal ", "Home ", "Work ", "Play ", "Health ‍♀️" , "Other"]
                $0.onChange { [unowned self] row in
                    self.todoItem?.category = row.value
                }
                }

            +++ Section(){ section in
                section.header = {
                    var header = HeaderFooterView<UIView>(.callback({
                        let button = UIButton(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
                        button.backgroundColor = .darkGray
                        button.setTitle("Save Item", for: .normal)
                        button.addTarget(self, action: #selector(self.buttonAction), for: .touchUpInside)

                        return button
                    }))
                    header.height = { 50 }
                    return header
                }()
        }
    }

    @objc func buttonAction(sender: UIButton!) {
        delegate?.dataReceived(data: todoItem!)
        self.dismiss(animated: true, completion: nil)

        print("Button tapped")
    }
}

The Todo type is defined as:

struct ToDo {

    var title: String?
    var description: String?
    var category : String?

}

And here is the ViewController that controls the main.storyboard :

import UIKit

let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)




class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CanReceive {

    var todoList = [ToDo]()
    let cellId = "CellId"
    let test = ["Row1", "Row2", "Row3"]
    let imageName = ["anchor", "arrow-down", "aperture"]
    @IBOutlet weak var ImageTop: UIImageView!
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: cellId)
        configureTableView()
        ImageTop.image = UIImage(named: "todo-background")


    }

    //data source method

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return todoList.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomTableViewCell
        cell.categoryLabel?.text = todoList[indexPath.row].category
        cell.descriptionLabel.text = todoList[indexPath.row].description
        var imageCategory = ""
        switch todoList[indexPath.row].category {
        case "Personal ":
            imageCategory = "Personal"
        case "Home ":
            imageCategory = "Home"
        case "Work ":
            imageCategory = "Work"
        case "Play ":
            imageCategory = "Play"
        case "Health ‍♀️":
            imageCategory = "Health"
        case "Other":
            imageCategory = "Other"
        default:
            break
        }
        cell.imageCategory?.image = UIImage(named: imageCategory)
        return cell
    }

    func configureTableView() {
        self.tableView.rowHeight = 80
        self.tableView.separatorStyle = .none

    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Did select row at index \(indexPath.row)")
        print("Row height is: \(tableView.rowHeight)")
    }
    @IBAction func addButtonPressed(_ sender: RoundButton) {
        let addItemViewController = AddItemViewController()
        addItemViewController.delegate = self
        self.present(addItemViewController, animated:true, completion:nil)
    }

 //Delegate 
    func dataReceived(data: ToDo) {
        todoList.append(data)
        return
    }
}

Thank you for your time and sorry if my question is so silly.

vadian
  • 274,689
  • 30
  • 353
  • 361
Bad Son
  • 131
  • 2
  • 16
  • Which line is throwing that error? – Dávid Pásztor Apr 18 '19 at 08:42
  • After enter the form and click the Save Item button, the app crash and said that Unexpectedly found nil while unwrapping an Optional value, I will add images for more illustration. – Bad Son Apr 18 '19 at 08:45
  • You should set up an exception breakpoint in Xcode, that will show you which exact line is crashing. Btw why are you using `unowned self` in your closures instead of `weak self`? – Dávid Pásztor Apr 18 '19 at 08:49
  • I removed the images because they were irrelevant. – vadian Apr 18 '19 at 09:03
  • Possible duplicate of [What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?](https://stackoverflow.com/questions/32170456/what-does-fatal-error-unexpectedly-found-nil-while-unwrapping-an-optional-valu) – user28434'mstep Apr 18 '19 at 09:32

2 Answers2

2

The error occurs because in AddItemViewController the property todoItem is declared but not initialized.

I recommend to use temporary variables for the three properties and create a ToDo instance when the button is pressed

class AddItemViewController : FormViewController {

    var delegate : CanReceive?

    var title, description, category : String?

    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d yyyy, h:mm a"
        return formatter
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        form +++ Section()
            <<< TextRow(){
                $0.title = "Title"
                $0.placeholder = "Enter text here"
                $0.onChange { [unowned self] row in
                    self.title = row.value
                }
            }
            <<< TextRow(){
                $0.title = "Description"
                $0.placeholder = "Give some description"
                $0.onChange { [unowned self] row in
                    self.description = row.value
                }
            }

            <<< AlertRow<String>() {
                $0.title = "Category"
                $0.selectorTitle = "Select the category"

                $0.options = ["Personal ", "Home ", "Work ", "Play ", "Health ‍♀️" , "Other"]
                $0.onChange { [unowned self] row in
                    self.category = row.value
                }
                }

            +++ Section(){ section in
                section.header = {
                    var header = HeaderFooterView<UIView>(.callback({
                        let button = UIButton(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
                        button.backgroundColor = .darkGray
                        button.setTitle("Save Item", for: .normal)
                        button.addTarget(self, action: #selector(self.buttonAction), for: .touchUpInside)

                        return button
                    }))
                    header.height = { 50 }
                    return header
                }()
        }
    }

    @objc func buttonAction(sender: UIButton) { // no implicit unwrapped optional
        let todoItem = ToDo(title: title, description: description, category : category)
        delegate?.dataReceived(data: todoItem)
        self.dismiss(animated: true, completion: nil)

        print("Button tapped")
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
1

I think this line delegate?.dataReceived(data: todoItem!) caused you the error, because you implicitly unwrapped todoItem while it is not initialized

Thomas Vu
  • 105
  • 1
  • 7