SwiftUI Tutorial: Displaying collection data with List View— Todolist App

Rizal Hilman
6 min readJul 20, 2023
SwiftUI List View

Building a List view has never been easier with SwiftUI! To create a List view, we just need to use the List view and define its content inside.

List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item ...")
}

Now, let's utilize List view to build a simple Todo list app. Although we won't build a fully functional todo list app, we will be able to show the todo list and open the todo list detail page. Here is a preview of our app:

We will have three views:

  • ContentView: the main view for displaying the todo list
  • StatusIndicator: showing the status of the backlog
  • TodoDetailView: detail todo list view

Getting Started

To get started, create a new SwiftUI project in Xcode. For this tutorial, we'll name our project "SwiftUI Simple TodoList.”

if you want to use my starter project, clone this project:

Data Model and Sample Data

Before we work on the UI, we need to prepare the data model and sample dataset. Here is the data model:

enum TodoStatus: String {
case pending
case completed
}

struct Todo: Identifiable, Hashable {
let id: Int
let title: String
let date: Date
var status: TodoStatus
}

// Sample data
let todos: [Todo] = [
// add your sample data here
]

Our data model consists of two structs: Todo and TodoStatus. The Todo struct is an identifiable and hashable struct that represents a todo item in our app. It contains an id, title, date, and status. The TodoStatus enum is used to represent the status of a todo item.

Then, create the todo list sample data. We update our todos variable inside Todo.swift, here is the update version of Todo.swift:

enum TodoStatus: String {
case pending
case completed
}

struct Todo: Identifiable, Hashable {
let id: Int
let title: String
let date: Date
var status: TodoStatus
}

// Helper function to create Date instances from components
func createDate(year: Int, month: Int, day: Int, hour: Int, minute: Int) -> Date {
var components = DateComponents()
components.year = year
components.month = month
components.day = day
components.hour = hour
components.minute = minute
components.timeZone = TimeZone.current
let calendar = Calendar.current
return calendar.date(from: components)!
}

// Sample data
let todos: [Todo] = [
Todo(id: 1, title: "Buy groceries", date: createDate(year: 2023, month: 7, day: 20, hour: 9, minute: 0), status: .pending),
Todo(id: 2, title: "Finish homework", date: createDate(year: 2023, month: 7, day: 21, hour: 15, minute: 30), status: .pending),
Todo(id: 3, title: "Call mom", date: createDate(year: 2023, month: 7, day: 22, hour: 12, minute: 0), status: .pending),
Todo(id: 4, title: "Go for a run", date: createDate(year: 2023, month: 7, day: 23, hour: 7, minute: 0), status: .pending),
Todo(id: 5, title: "Read a book", date: createDate(year: 2023, month: 7, day: 19, hour: 18, minute: 0), status: .completed),
Todo(id: 6, title: "Write a blog post", date: createDate(year: 2023, month: 7, day: 18, hour: 20, minute: 0), status: .completed),
Todo(id: 7, title: "Attend a meeting", date: createDate(year: 2023, month: 7, day: 25, hour: 10, minute: 0), status: .pending),
Todo(id: 8, title: "Clean the house", date: createDate(year: 2023, month: 7, day: 17, hour: 14, minute: 0), status: .completed),
Todo(id: 9, title: "Practice playing guitar", date: createDate(year: 2023, month: 7, day: 26, hour: 16, minute: 0), status: .pending),
Todo(id: 10, title: "Plan a trip", date: createDate(year: 2023, month: 7, day: 27, hour: 11, minute: 30), status: .pending)
]

Our sample data consists of 10 todo items. Each item has a unique id, a title, a date, and a status. The createDatefunction is a helper function that creates a Date instance from its components.

Building the TodoDetailView

The TodoDetailView is a view that displays the details of a todo item. It takes a Todo instance as a parameter and displays the title, date, and status. Here's the code:

import SwiftUI

struct TodoDetailView: View {
var todo: Todo

var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(todo.title)
.font(.title)
Text(formatDate(todo.date))
.font(.subheadline)
.foregroundColor(.gray)
StatusIndicator(status: todo.status)
}
.padding()
.navigationTitle("Todo Details")
}

private func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: date)
}
}

The TodoDetailView has a VStack with Text, Text, and StatusIndicator views. The formatDate function is a helper function that formats the date property of a Todo instance. This view also has a title that appears in the navigation bar.

Implementing the StatusIndicator View

The StatusIndicator view is a view that displays the status of a todo item. It takes a TodoStatus instance as a parameter and displays the status as a colored label. Here's the code:

import SwiftUI

struct StatusIndicator: View {
var status: TodoStatus

var body: some View {
let backgroundColor: Color = {
switch status {
case .completed:
return Color.green
case .pending:
return Color.orange
}
}()

Text(status == .completed ? "Completed" : "Pending")
.font(.footnote)
.foregroundColor(.white)
.padding(8)
.background(backgroundColor)
.clipShape(Capsule())
}
}

The StatusIndicator view has a Text view that displays the status of a todo item. The background color of the Textview depends on the status of the todo item.

Building the ContentView

The ContentView is the main view of our app. It displays the todo list using a List view. Each todo item is displayed using a NavigationLink view that opens the TodoDetailView when tapped. Here's the code:

import SwiftUI

struct ContentView: View {

var body: some View {
NavigationView {
List {
ForEach(todos, id: \.self) { todo in
NavigationLink(destination: TodoDetailView(todo: todo)) {
HStack(alignment: .center) {
VStack(alignment: .leading) {
Text(todo.title)
.font(.title3)
Text(formatDate(todo.date))
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
StatusIndicator(status: todo.status)
}
}
}
}
.listStyle(.inset)
.padding()
.navigationTitle("Todo List")
}
}

private func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: date)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

The ContentView has a List view that displays the todo list using a ForEach loop. Each todo item is displayed using a NavigationLink view that opens the TodoDetailView when tapped. The NavigationLink view has a HStack with a VStack and a StatusIndicator view. This view also has a title that appears in the navigation bar.

Run the app

Now, run the code on the Simulator and you will get the expected result if you follow the instructions correctly!

Wrap Up

Congratulations! You have successfully built a simple Todo list app using SwiftUI. You've learned how to define data models, use SwiftUI views, and implement navigation between different screens. Now, feel free to extend the app's functionality or explore more SwiftUI features to create even more sophisticated iOS applications.

Finsh Project

--

--

Rizal Hilman

Tech Mentor at Apple Developer Academy - Batam | Apple Swift Certified Trainer | Apple Professional Learning Specialist | Apple Teacher | WWDC19 Winner