ToDo swift app with closure and UserNotifications

avelynhc

Avelyn Hyunjeong Choi

Posted on November 27, 2023

ToDo swift app with closure and UserNotifications

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

  • Link + button to second view controller

Image description

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

Image description

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

Image description

UI now should be like this
Image description

2.Give an identifier to Table View Cell, ToDoCell

Image description

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

Image description

4.Link ToDoCell with ToDoTVCell

Image description

5.Create outlets for two labels in the ToDoTVCell class

Image description

import UIKit

class ToDoTVCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var dateLabel: UILabel!
}
Enter fullscreen mode Exit fullscreen mode

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

Image description

7.Define the struct

import Foundation

struct ToDoItem {
    let title: String
    let deadline: Date
}
Enter fullscreen mode Exit fullscreen mode

8.AddToDoVC code

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

9.View controller code

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

10.Demo

Image description

Image description

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

Image description

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

Image description

💖 💪 🙅 🚩
avelynhc
Avelyn Hyunjeong Choi

Posted on November 27, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related