SwiftUI Tutorial: Displaying collection data with List View— Todolist App
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 createDate
function 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 Text
view 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.