Search code examples
swiftdelegatespass-dataoptional-values

Unexpectedly found nil while unwrapping an Optional value when passing data backward


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.


Solution

  • 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")
        }
    }