ToDo swift app with closure and UserNotifications
Tags: swift, mobile, beginners
Published: 2023-11-27 16:33:06

**Local notifications** in iOS are a way for an app to present information to the user while it's not in the foreground. They are used to display alerts, play sounds, or badge the app icon

**1.Set up the UI first**

- In the view controller, `+` can be added

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lv984qes7jw1qhverwkd.png)

- Link `+` button to second view controller

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mc3xo1kg0s86mi7nkgx9.png)

- In the view controller, enter 1 to prototype cells and add labels

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppf529wnb88aldu2na6c.png)

- In the second view controller, date picker can be set up as below

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/si6tjjv0cyiqrqd4qgkr.png)

UI now should be like this
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xg7u1kc58gfkh61br2yx.png)

**2.Give an identifier to Table View Cell, `ToDoCell`**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqqbpaj8m1avhdwwqdut.png)

**3.Create a separate class `ToDoTVCell` for the Table View Cell, `ToDoCell` we just created**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0es0mg9blhyrmci42ta1.png)

**4.Link `ToDoCell` with `ToDoTVCell`**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmtxmtx3y448v4fulusn.png)

**5.Create outlets for two labels in the `ToDoTVCell` class**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oj27qsyy3h2mrjpn0dc.png)

```swift
import UIKit

class ToDoTVCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var dateLabel: UILabel!
}
```

**6.Create a class file, `AddToDoVC` and link with the view controller**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/084dmk6dssicyojup08h.png)

**7.Define the struct**
```swift
import Foundation

struct ToDoItem {
    let title: String
    let deadline: Date
}
```

**8.AddToDoVC code**
```swift
import UIKit

class AddToDoVC: UIViewController {

    @IBOutlet weak var titleTextField: UITextField!
    @IBOutlet weak var deadlineDatePicker: UIDatePicker!
    
    // closure comes to rescue for passing the new item
    // to able to add this to an array (toDoItems in the ViewController)
    // we use type function - optional
    var onSave: ((ToDoItem) -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func addToDo(_ sender: UIButton) {
        guard let title = titleTextField.text, !title.isEmpty else {
            return
        }
        let deadline = deadlineDatePicker.date
        
        print(deadline)
        
        // create an instnace of new item
        let newItem = ToDoItem(title: title, deadline: deadline)
        
        // use closure
        onSave?(newItem)
        
        // go back to main
        self.navigationController?.popViewController(animated: true)
    }
}
```

**9.View controller code**
```swift
import UIKit

// import this for notification
import UserNotifications

class ViewController: UIViewController {
    
    var toDoItems: [ToDoItem] = [] {
        didSet {
            tableView.reloadData()
        }
    }
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
        tableView.delegate = self
    }
    
    func scheduleNotification(for item: ToDoItem) {
        let content = UNMutableNotificationContent()
        content.title = "ToDo Reminder"
        content.body = "Don't forget: \(item.title)"
        content.sound = .default
        
        // get the DateComponents for dateMatching for creating a trigger
        let calender = Calendar.current
        let components = calender.dateComponents([.year, .month, .day, .hour, .minute], from: item.deadline)
        
        // create a trigger
        let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
        
        // to set the notification -> we need to create a notification request
        // don't use item.title for the indentifier. not recommended. maybe uuid
        let request = UNNotificationRequest(identifier: item.title, content: content, trigger: trigger)
        
        // add the request to the notification center
        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("Error scheduling notification: \(error)")
            }
        }
    }
    
    // *use closure here
    // how to transfer data from destination to source
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let addToDoVC = segue.destination as? AddToDoVC else {
            return
        }
        
        // will be invoked when onSave() was called in addToDo() of AddToDoVC using closure
        // you can assign code block to onSave()
        addToDoVC.onSave = { newItem in
            self.toDoItems.append(newItem)
            
            // schedule notification
            self.scheduleNotification(for: newItem)
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return toDoItems.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoCell", for: indexPath) as? ToDoTVCell else { return UITableViewCell() }
        
        let toDoItem = toDoItems[indexPath.row]
        
        // date formatter
        let formatter = DateFormatter()
        formatter.dateFormat = "dd-MM-yyyy HH:mm"
        let formmattedDate = formatter.string(from: toDoItem.deadline)
        
        cell.titleLabel.text = toDoItem.title
        cell.dateLabel.text = formmattedDate
        
        return cell
    }
    
    // delete the data -> remove pending notification as well
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            // cancel the notification
            let item = toDoItems[indexPath.row]
            UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [item.title])
            
            // remove the item from the data source
            toDoItems.remove(at: indexPath.row)
        }
    }
}
```

10.Add code to get the authorization from users whether they will allow notification or not

```swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    // this one is the first one invoked when running the application
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        // get the authorization from user to allow notification
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.alert, .sound]) { granted, error in
            // handle the response if needed
            // command + shift + h -> minimize the app to see if notification works!
            
        }
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
}
```

**10.Demo**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rf77mbvkbkxq8qbaec5l.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ga2vuh2omm7clkykahxr.png)

minimize the app to see whether notification works using `cmd + shift + h`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lko2okzqsm23ov71046p.png)

you can delete the row as well, which will also delete the pending notification

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2393zfoqt12af404vuv.png)