Creating a Simple To-Do List App: A Guide for New iOS Developers

by Tutwow

Introduction

As a new iOS developer, creating a simple to-do list app is an excellent way to get started with app development. This project will help you understand the basics of iOS programming, user interface design, and data persistence. In this comprehensive guide, we’ll walk you through the process of building a functional to-do list app using Swift and Xcode.

By the end of this tutorial, you’ll have created an app that allows users to add, edit, and delete tasks, as well as mark them as complete. We’ll cover essential concepts such as:

  • Setting up your development environment
  • Creating a user interface with Interface Builder
  • Implementing basic Swift programming concepts
  • Working with table views and custom cells
  • Persisting data using UserDefaults
  • Implementing CRUD (Create, Read, Update, Delete) operations

Let’s dive in and start building your first iOS app!

Setting Up Your Development Environment

Before we begin coding, we need to ensure that you have the necessary tools installed on your Mac.

1. Install Xcode

Xcode is Apple’s integrated development environment (IDE) for creating iOS, macOS, watchOS, and tvOS applications. To install Xcode:

  1. Open the App Store on your Mac
  2. Search for “Xcode”
  3. Click “Get” or the download button
  4. Wait for the installation to complete

2. Create a New Xcode Project

Once Xcode is installed, follow these steps to create a new project:

  1. Launch Xcode
  2. Click “Create a new Xcode project” or go to File > New > Project
  3. Choose “App” under the iOS tab
  4. Click “Next”
  5. Name your project “ToDoListApp”
  6. Choose a location to save your project
  7. Click “Create”

Designing the User Interface

Now that we have our project set up, let’s design the user interface for our to-do list app.

1. Set Up the Main Storyboard

  1. Open the Main.storyboard file in your project navigator
  2. Delete the existing view controller
  3. Drag a Table View Controller from the Object Library onto the storyboard
  4. Select the Table View Controller and go to the Attributes Inspector
  5. Check the “Is Initial View Controller” box

2. Create a Custom Table View Cell

  1. Right-click on your project folder in the navigator
  2. Select New File > Cocoa Touch Class
  3. Name the class “TodoItemCell” and make it a subclass of UITableViewCell
  4. Open TodoItemCell.swift and add the following code:

    “`swift
    class TodoItemCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var checkmarkButton: UIButton!
    }
    “`

  5. Go back to Main.storyboard
  6. Select the prototype cell in the Table View Controller
  7. Set the cell’s class to TodoItemCell in the Identity Inspector
  8. Set the cell’s identifier to “TodoItemCell” in the Attributes Inspector
  9. Add a Label and a Button to the cell
  10. Connect the Label to the titleLabel outlet and the Button to the checkmarkButton outlet in the Assistant Editor

3. Add a Navigation Controller

  1. Select the Table View Controller in the storyboard
  2. Go to Editor > Embed In > Navigation Controller

4. Create an Add Task View Controller

  1. Drag a new View Controller onto the storyboard
  2. Add a Text Field and a “Save” Button to the new View Controller
  3. Create a segue from the “+” button in the Navigation Bar of the Table View Controller to the new View Controller
  4. Set the segue identifier to “addTask”

Implementing the To-Do List Functionality

Now that we have our user interface set up, let’s implement the core functionality of our to-do list app.

1. Create a Todo Item Model

Create a new Swift file called "TodoItem.swift" and add the following code:

struct TodoItem: Codable {
var title: String
var isCompleted: Bool

init(title: String, isCompleted: Bool = false) {
self.title = title
self.isCompleted = isCompleted
}
}

2. Implement the Table View Controller

Rename the ViewController.swift file to TodoListViewController.swift and update its content with the following code:

import UIKit

class TodoListViewController: UITableViewController {

var todoItems: [TodoItem] = []

override func viewDidLoad() {
super.viewDidLoad()

title = "To-Do List"
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addNewTask))

loadTodoItems()
}

// MARK: - Table view data source

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TodoItemCell", for: indexPath) as! TodoItemCell

let item = todoItems[indexPath.row]
cell.titleLabel.text = item.title
cell.checkmarkButton.isSelected = item.isCompleted

cell.checkmarkButton.addTarget(self, action: #selector(toggleCompletion(_:)), for: .touchUpInside)
cell.checkmarkButton.tag = indexPath.row

return cell
}

// MARK: - Table view delegate

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
todoItems.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
saveTodoItems()
}
}

// MARK: - Actions

@objc func addNewTask() {
performSegue(withIdentifier: "addTask", sender: nil)
}

@objc func toggleCompletion(_ sender: UIButton) {
let index = sender.tag
todoItems[index].isCompleted.toggle()
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
saveTodoItems()
}

// MARK: - Data Persistence

func saveTodoItems() {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(todoItems) {
UserDefaults.standard.set(encoded, forKey: "todoItems")
}
}

func loadTodoItems() {
if let savedItems = UserDefaults.standard.object(forKey: "todoItems") as? Data {
let decoder = JSONDecoder()
if let loadedItems = try? decoder.decode([TodoItem].self, from: savedItems) {
todoItems = loadedItems
}
}
}
}

3. Implement the Add Task View Controller

Create a new file called AddTaskViewController.swift and add the following code:

import UIKit

protocol AddTaskDelegate: AnyObject {
func addTaskViewController(_ controller: AddTaskViewController, didSaveTask task: TodoItem)
}

class AddTaskViewController: UIViewController {

@IBOutlet weak var taskTextField: UITextField!

weak var delegate: AddTaskDelegate?

override func viewDidLoad() {
super.viewDidLoad()
taskTextField.becomeFirstResponder()
}

@IBAction func saveTask(_ sender: Any) {
guard let title = taskTextField.text, !title.isEmpty else {
// Show an alert if the text field is empty
let alert = UIAlertController(title: "Error", message: "Please enter a task", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
return
}

let newTask = TodoItem(title: title)
delegate?.addTaskViewController(self, didSaveTask: newTask)
navigationController?.popViewController(animated: true)
}
}

4. Update the TodoListViewController

Add the following extension to TodoListViewController.swift to implement the AddTaskDelegate:

extension TodoListViewController: AddTaskDelegate {
func addTaskViewController(_ controller: AddTaskViewController, didSaveTask task: TodoItem) {
todoItems.append(task)
let indexPath = IndexPath(row: todoItems.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
saveTodoItems()
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addTask" {
let navigationController = segue.destination as! UINavigationController
let addTaskViewController = navigationController.topViewController as! AddTaskViewController
addTaskViewController.delegate = self
}
}
}

Enhancing the User Experience

Now that we have the basic functionality in place, let’s add some features to improve the user experience.

1. Add Swipe Actions

Update the TodoListViewController.swift file to include swipe actions for completing and deleting tasks:

override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let completeAction = UIContextualAction(style: .normal, title: "Complete") { (action, view, completionHandler) in
self.todoItems[indexPath.row].isCompleted.toggle()
self.tableView.reloadRows(at: [indexPath], with: .automatic)
self.saveTodoItems()
completionHandler(true)
}
completeAction.backgroundColor = .systemGreen

return UISwipeActionsConfiguration(actions: [completeAction])
}

override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (action, view, completionHandler) in
self.todoItems.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.saveTodoItems()
completionHandler(true)
}

return UISwipeActionsConfiguration(actions: [deleteAction])
}

2. Implement Task Editing

Add the following method to TodoListViewController.swift to allow editing of existing tasks:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alert = UIAlertController(title: "Edit Task", message: nil, preferredStyle: .alert)
alert.addTextField { textField in
textField.text = self.todoItems[indexPath.row].title
}

let saveAction = UIAlertAction(title: "Save", style: .default) { _ in
if let newTitle = alert.textFields?.first?.text, !newTitle.isEmpty {
self.todoItems[indexPath.row].title = newTitle
self.tableView.reloadRows(at: [indexPath], with: .automatic)
self.saveTodoItems()
}
}

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

alert.addAction(saveAction)
alert.addAction(cancelAction)

present(alert, animated: true, completion: nil)
}

3. Add Task Prioritization

Update the TodoItem struct in TodoItem.swift to include a priority property:

struct TodoItem: Codable {
var title: String
var isCompleted: Bool
var priority: Priority

enum Priority: Int, Codable {
case low = 0
case medium = 1
case high = 2
}

init(title: String, isCompleted: Bool = false, priority: Priority = .medium) {
self.title = title
self.isCompleted = isCompleted
self.priority = priority
}
}

Update the TodoItemCell to display the priority:

class TodoItemCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var checkmarkButton: UIButton!
@IBOutlet weak var priorityLabel: UILabel!

func configure(with item: TodoItem) {
titleLabel.text = item.title
checkmarkButton.isSelected = item.isCompleted

switch item.priority {
case .low:
priorityLabel.text = "Low"
priorityLabel.textColor = .systemBlue
case .medium:
priorityLabel.text = "Medium"
priorityLabel.textColor = .systemOrange
case .high:
priorityLabel.text = "High"
priorityLabel.textColor = .systemRed
}
}
}

Update the cellForRowAt method in TodoListViewController.swift:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TodoItemCell", for: indexPath) as! TodoItemCell

let item = todoItems[indexPath.row]
cell.configure(with: item)

cell.checkmarkButton.addTarget(self, action: #selector(toggleCompletion(_:)), for: .touchUpInside)
cell.checkmarkButton.tag = indexPath.row

return cell
}

4. Implement Task Sorting

Add a sorting function to TodoListViewController.swift:

func sortTasks() {
todoItems.sort { (item1, item2) -> Bool in
if item1.isCompleted != item2.isCompleted {
return !item1.isCompleted
} else if item1.priority.rawValue != item2.priority.rawValue {
return item1.priority.rawValue > item2.priority.rawValue
} else {
return item1.title < item2.title
}
}
tableView.reloadData()
saveTodoItems()
}

Call this function after adding, editing, or toggling completion of tasks.

5. Add Task Due Dates

Update the TodoItem struct to include a due date:

struct TodoItem: Codable {
var title: String
var isCompleted: Bool
var priority: Priority
var dueDate: Date?

enum Priority: Int, Codable {
case low = 0
case medium = 1
case high = 2
}

init(title: String, isCompleted: Bool = false, priority: Priority = .medium, dueDate: Date? = nil) {
self.title = title
self.isCompleted = isCompleted
self.priority = priority
self.dueDate = dueDate
}
}

Update the TodoItemCell to display the due date:

class TodoItemCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var checkmarkButton: UIButton!
@IBOutlet weak var priorityLabel: UILabel!
@IBOutlet weak var dueDateLabel: UILabel!

func configure(with item: TodoItem) {
titleLabel.text = item.title
checkmarkButton.isSelected = item.isCompleted

switch item.priority {
case .low:
priorityLabel.text = "Low"
priorityLabel.textColor = .systemBlue
case .medium:
priorityLabel.text = "Medium"
priorityLabel.textColor = .systemOrange
case .high:
priorityLabel.text = "High"
priorityLabel.textColor = .systemRed
}

if let dueDate = item.dueDate {
let formatter = DateFormatter()
formatter.dateStyle = .short
dueDateLabel.text = formatter.string(from: dueDate)
} else {
dueDateLabel.text = "No due date"
}
}
}

Update the AddTaskViewController to include a date picker for setting due dates:

class AddTaskViewController: UIViewController {

@IBOutlet weak var taskTextField: UITextField!
@IBOutlet weak var prioritySegmentedControl: UISegmentedControl!
@IBOutlet weak var dueDatePicker: UIDatePicker!

weak var delegate: AddTaskDelegate?

override func viewDidLoad() {
super.viewDidLoad()
taskTextField.becomeFirstResponder()
}

@IBAction func saveTask(_ sender: Any) {
guard let title = taskTextField.text, !title.isEmpty else {
// Show an alert if the text field is empty
let alert = UIAlertController(title: "Error", message: "Please enter a task", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
return
}

let priority = TodoItem.Priority(rawValue: prioritySegmentedControl.selectedSegmentIndex) ?? .medium
let dueDate = dueDatePicker.date

let newTask = TodoItem(title: title, priority: priority, dueDate: dueDate)
delegate?.addTaskViewController(self, didSaveTask: newTask)
navigationController?.popViewController(animated: true)
}
}

Conclusion

Congratulations! You’ve successfully created a feature-rich to-do list app for iOS. This project has covered many essential aspects of iOS development, including:

  • Setting up an Xcode project
  • Designing user interfaces with Interface Builder
  • Working with table views and custom cells
  • Implementing CRUD operations
  • Persisting data using UserDefaults
  • Adding swipe actions and editing functionality
  • Implementing task prioritization and sorting
  • Working with date pickers and formatting dates

As you continue to develop your skills, consider adding more advanced features to your app, such as:

  • Implementing Core Data for more robust data persistence
  • Adding push notifications for task reminders
  • Implementing user authentication and cloud synchronization
  • Creating a widget for quick task management
  • Adding support for recurring tasks

Remember that becoming proficient in iOS development takes time and practice. Keep building projects, exploring new APIs, and staying up-to-date with the latest iOS technologies to improve your skills and create even more impressive apps in the future.

FAQs

Q: What is Swift, and why is it used for iOS development?

A: Swift is a powerful and intuitive programming language developed by Apple for iOS, macOS, watchOS, and tvOS. It’s designed to be fast, safe, and expressive, making it an excellent choice for building modern apps. Swift offers features like type inference, optionals, and functional programming constructs, which help developers write cleaner and more maintainable code.

Q: Can I use this app as a starting point for a more complex project?

A: Absolutely! This to-do list app serves as an excellent foundation for more advanced projects. You can build upon this base by adding features like cloud synchronization, multiple lists, or integrations with other productivity tools.

Q: How can I improve the app’s performance when dealing with a large number of tasks?

A: To improve performance with a large number of tasks, consider implementing the following:

  • Use Core Data instead of UserDefaults for more efficient data storage and retrieval
  • Implement pagination or infinite scrolling in the table view
  • Optimize table view cell reuse to reduce memory usage
  • Use background queues for time-consuming operations like sorting or filtering tasks

Q: How can I add custom icons or images to my app?

A: To add custom icons or images:

  1. Create or obtain the desired images in various sizes required for iOS apps
  2. Add the images to your Xcode project’s asset catalog
  3. Use the images in your app by referencing their names in Interface Builder or programmatically using UIImage(named:)

Q: What are some recommended resources for learning more about iOS development?

A: Here are some excellent resources for expanding your iOS development knowledge:

  • Apple’s official Swift documentation and tutorials
  • Ray Wenderlich’s iOS tutorials and books
  • Stanford’s CS193p course on iOS development (available on iTunes U)
  • Hacking with Swift by Paul Hudson
  • iOS Dev Weekly newsletter for staying up-to-date with the latest trends and news

Q: How can I test my app on a physical iOS device?

A: To test your app on a physical iOS device:

  1. Connect your iOS device to your Mac using a USB cable
  2. In Xcode, select your device from the scheme menu in the toolbar
  3. Ensure your Apple ID is set up in Xcode’s Accounts preferences
  4. Click the “Run” button or press Cmd+R to build and run your app on the device

Note that you may need to trust your developer certificate on the iOS device the first time you run your app.

By following this comprehensive guide and exploring the additional resources mentioned, you’ll be well on your way to becoming a proficient iOS developer. Remember to practice regularly, stay curious, and don’t hesitate to experiment with new ideas and technologies as you continue your journey in app development.

You may also like

Leave a Comment