---
title: SwiftUI: Task App Quickstart
slug: v4-4/NYLU_Yy8AHbGPPEi-06YE
docTags: 
createdAt: 2023-07-28T04:17:21.729Z
---

This quickstart provides step-by-step instructions for using SwiftUI to create a task app in Xcode, which consists of the following high-level steps:

1. [Creating a New App in Xcode](./#creating-a-new-app-in-xcode)
2. [Configuring Ditto](./#configuring-ditto)
3. [Showing the List of Tasks](./#showing-the-list-of-tasks)
4. [Editing Tasks](./#editing-tasks)

# Prequisites

- Ditto account and access credentials
- iOS 13 (or later)
- macOS 11 (or later)
- [Xcode](https://apps.apple.com/us/app/xcode/id497799835) 15 (or later)

:::hint{type="info"}
For instructions on creating your account and obtaining your credentials, see [Onboarding](docId\:JQjYL9gbvsgI9vlW3Ywvc).
:::

# Creating a New App in Xcode

:::::WorkflowBlock
:::WorkflowBlockItem
Click **File**, and then selec&#x74;**&#x20;New Project**.&#x20;
:::

:::WorkflowBlockItem
In the **Choose a template for your new project&#x20;**&#x6D;odal, select **App** and then click **Next**.

::Image[]{src="https://api.archbee.com/api/optimize/qoRkNxW5fJ81r_NqVpc8C/dxpijns3wXn_h7XG8T0fx_image.png" size="90" width="3024" height="2024" position="flex-start" showCaption="false"}
:::

::::WorkflowBlockItem
In the **Choose options for your new project&#x20;**&#x6D;odal, enter the following information as appropriate and then click **Next**.&#x20;

:::hint{type="info"}
The following are merely suggestions; enter any information in the form that you desire.
:::

The following steps provide suggested values for the form; however you can enter any information you desire:

1. For **Product Name**, type "Tasks".
2. For **Team**, select **DittoLive Incorporated**.
3. For **Organization Identifier**, type "live.ditto".
4. For **Interface**, select **SwiftUI**.
5. For **Life Cycle**, select **Swift UI App**.
6. For **Language**, select **Swift**.

![](https://api.archbee.com/api/optimize/qoRkNxW5fJ81r_NqVpc8C/fdTqJkcdHkSuekZBzj1IU_image.png)
::::
:::::

# Configuring Ditto

:::::WorkflowBlock
:::WorkflowBlockItem
## Add ditto to TasksApp.swift

When Xcode generated your project, there should be a file called **TasksApp.swift**.&#x20;

1. First import Ditto with `import DittoSwift`
2. Construct an instance of Ditto with an online playground identity using the APP ID of the app that you just created on the portal. We are using an `.onlinePlayground` setup, which should suffice for this tutorial. However, you should never deploy this to a production environment like the Apple App Store.
3. We will call `startSync` as soon as the app's `ContentView` appears. Add two `@State` variables to capture if `ditto.startSync` throws an error. One variable will be `@State var isPresentingAlert = false` and the other is a `@State var errorMessage = ""`.
4. Add an `.onAppear` function and give it a playground token. Look for `"YOUR_APP_ID_HERE"` and insert your valid token. You can get a token from our [Big Peer portal](https://portal.ditto.live). If the `startSync()` fails, we will set `isPresentingAlert = true` and set the `errorMessage` to the error's `.localizedDescription`.
5. We will then present a `.alert` if `isPresentingAlert` is true. Notice that we will pass a `@State` variable as a binding type, which is why we denoted `$isPresentingAlert` prefixed with a `$`. To learn more about SwiftUI's `Binding` types like `@State` [click here](https://developer.apple.com/tutorials/app-dev-training/managing-data-flow-between-views).

```swift
import SwiftUI
// 1.
import DittoSwift

@main
struct TasksApp: App {

    // 2.
    var ditto = Ditto(identity: .onlinePlayground(appID: "YOUR_APP_ID_HERE", token: "YOUR_TOKEN_HERE"))

    // 3.
    @State var isPresentingAlert = false
    @State var errorMessage = ""

    var body: some Scene {
        WindowGroup {
            TasksListScreen(ditto: ditto)

                // 4
                .onAppear(perform: {
                    do {
                        try ditto.startSync()
                    } catch (let err){
                        isPresentingAlert = true
                        errorMessage = err.localizedDescription
                    }
                })
                // 5.
                .alert(isPresented: $isPresentingAlert) {
                    Alert(title: Text("Uh Oh"), message: Text("There was an error trying to start the sync. Here's the error \(errorMessage) Ditto will continue working as a local database."), dismissButton: .default(Text("Got it!")))
                }
        }
    }
}
```
:::

::::WorkflowBlockItem
## Create a Task Struct

Ditto is a document database, which represents all of its rows in the database a JSON-like structure. In this tutorial, we will define each task like so:

```none
{
  "_id": "123abc",
  "body": "Get Milk",
  "isCompleted": true
}
```

These Task documents will all be in the "tasks" collection. We will be referencing this collection throughout this tutorial with:

```swift
let tasksCollection = ditto.store["tasks"]
```

Ditto documents have a flexible structure. Oftentimes, in strongly-typed languages like Swift, we will create a data structure give more definition to the app.
Create a new Swift file called **Task.swift** in your project.

1. Add `import DittoSwift` to the top of the file.
2. Add the matching variables `let _id: String`, `let body: String`, and `let isCompleted: Bool` to the struct. We will use this to match the document values to to the struct.
3. Add an `init` constructor to `Task` that takes in a `DittoDocument`
4. In the `init` constructor, parse out the document's keys with Ditto's type safe value accessors. This will safely map all the document's values to the struct's variables that we created in step 2.
5. Add a second `init` constructor to `Task` that just takes a `String` and a `bool`
6. Add an `extension Task: Identifiable` right below the `Task` struct definition and implement `var id: String` to return the `_id` key. We add the `Identifiable` protocol to assist SwiftUI's `List` view and `ForEach` component in later sections. Collection views in SwiftUI require knowing if a view is unique to prevent wasteful redraws. While it may seem confusing, we are only allowing the protocol to read the `_id` that we added in step 2.

:::CodeblockTabs
Task.swift

```swift
// 1.
import DittoSwift

struct Task {

    // 2.
    let _id: String
    let body: String
    let isCompleted: Bool

    // 3.
    init(document: DittoDocument) {
        // 4.
        _id = document["_id"].stringValue
        body = document["body"].stringValue
        isCompleted = document["isCompleted"].boolValue
    }
    
    // 5.
    init(body: String, isCompleted: Bool) {
        self._id = UUID().uuidString
        self.body = body
        self.isCompleted = isCompleted
    }
}

// 6.
extension Task: Identifiable {
    var id: String {
        return _id
    }
}
```
:::

This data class takes a `DittoDocument` and safely parses out the values into native Swift types. We also added constructor that allows us to preview data without requiring Ditto.

So now in our application if we want an array of `Tasks`, `[Task]`, we can write the following code:

```swift
let tasks: [Task] = ditto.store["tasks"].find("!isDeleted").exec().map({ Task(document: $0) })
```

Once we set up our user interface, you'll notice that reading these values becomes a bit easier with this added structure.
::::

::::WorkflowBlockItem
## Create a TasksListScreen view

When we generated the project, Xcode created a default **ContentView**, which need to swap out for a better starter view. Let's create a view called **TasksListScreen** which will show the list of the views.

1. Create a new **SwiftUI View** View by clicking **File > New > SwiftUI View**[​](https://docs.ditto.live/ios/tutorial/swift/configure-ditto#2-5-create-a-taskslistscreen-view)
2. Name it **"TasksListScreen"**
3. At the top of the new **TasksListScreen.**&#x41;dd Ditto with `import DittoSwift` at the top of the file
4. Create a constructor and a variable to pass `var ditto: Ditto`
5. Replace the `body` with `NavigationView` with a single `List` child. We will fill out the contents of the `List` in the next section. We've also added a couple of decorative navigation elements which we will hook up later. This includes a navigation title, `.navigationTitle` which shows the name of the app, a navigation plus button in `.navigationBarItems` and a `.sheet` that we will use navigate to an `EditScreen`. We will create the `EditScreen` later.



:::CodeblockTabs
TasksListScreen.swift

```swift
import SwiftUI
// 3.
import DittoSwift

struct TasksListScreen: View {

    // 4.
    let ditto: Ditto

    init(ditto: Ditto) {
        self.ditto = ditto
    }

    var body: some View {
        // 5.
        NavigationView {
            List {

            }
            .navigationTitle("Tasks - SwiftUI")
            .navigationBarItems(trailing: Button(action: {

            }, label: {
                Image(systemName: "plus")
            }))
            .sheet(isPresented: .constant(false), content: {

            })
        }
    }
}
```
:::
::::
:::::

# Showing the List of Tasks

In the last part of the tutorial we referenced a class called `TasksListScreen`. This screen will show a `List<Task>` using a JetPack Compose Column.

:::::WorkflowBlock
::::WorkflowBlockItem
## Create a TaskRow views

Each row of the tasks will be represented by a SwiftUI `View` called `TaskRow` which takes in a `Task` and two callbacks which we will use later.

1. If the `task.isCompleted` is `true`, we will show a filled circle icon and a
   strikethrough style for the `body`.
2. If the `task.isCompleted` is `false`, we will show an open circle icon.
3. If the user taps the `Icon`, we will call a `onToggle: ((_ task: Task) -> Void)?`, we will reverse the `isCompleted` from `true` to `false` or `false` to `true`
4. If the user taps the `Text`, we will call a `onClickBody: ((_ task: Task) -> Void)?`. We will use this to navigate an `EditScreen` (we will create this later)

For brevity, we will skip discussions on styling as it's best to see the code snippet below:

We've also included a `TaskRow_Previews` that allows you to see the end result with some test data quickly.

![](https://api.archbee.com/api/optimize/qoRkNxW5fJ81r_NqVpc8C/Za6AxDqputILOUVkMmB2k_image.png)

:::CodeblockTabs
TaskRow\.swift

```swift
import SwiftUI

struct TaskRow: View {

    let task: Task

    var onToggle: ((_ task: Task) -> Void)?
    var onClickBody: ((_ task: Task) -> Void)?

    var body: some View {
        HStack {
            // 2.
            Image(systemName: task.isCompleted ? "circle.fill": "circle")
                .renderingMode(.template)
                .foregroundColor(.accentColor)
                .onTapGesture {
                    onToggle?(task)
                }
            if task.isCompleted {
                Text(task.body)
                    // 2.
                    .strikethrough()
                    .onTapGesture {
                        onClickBody?(task)
                    }

            } else {
                // 3.
                Text(task.body)
                    .onTapGesture {
                        onClickBody?(task)
                    }
            }

        }
    }
}

struct TaskRow_Previews: PreviewProvider {
    static var previews: some View {
        List {
            TaskRow(task: Task(body: "Get Milk", isCompleted: true))
            TaskRow(task: Task(body: "Do Homework", isCompleted: false))
            TaskRow(task: Task(body: "Take out trash", isCompleted: true))
        }
    }
}
```
:::
::::

::::WorkflowBlockItem
## Create a TasksListScreenViewModel

In the world of SwiftUI, the most important design pattern is the MVVM, which stands for Model-View-ViewModel. MVVM strives to separate all data manipulation (Model and ViewModel) and data presentation (UI or View) into distinct areas of concern. When it comes to Ditto, we recommend that you never include references to edit `ditto` in `View.body`. All interactions with `ditto` for `upsert`, `update`, `find`, `remove` and `observe` should be within a `ViewModel`. The View should only render data from observable variables from the `ViewModel` and only the `ViewModel` should make direct edits to these variables.

Typically we create a `ViewModel` per screen or per page of an application. For the `TasksListScreen` we need some functionality like:

- Showing a realtime list of `Task` objects
- Triggering an intention to edit a `Task`
- Triggering an intention to create a `Task`
- Clicking an icon to toggle the icon from `true` to `false` or `false` to `true`

In SwiftUI we create a view model by inheriting the `ObservableObject`. The `ObservableObject` allows SwiftUI to watch changes to certain variables to trigger view updates intelligently. To learn more about `ObservableObject` we recommend this excellent [tutorial from Hacking with Swift](https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-observedobject-to-manage-state-from-external-objects).

1. Create a file called **TasksListScreenViewModel.swift** in your project
2. Add an `init` constructor to pass in a `ditto: Ditto` instance and store it in a local variable.
3. Create two `@Published` variables for `tasks` and i`sPresentingEditScreen`. `@Published` variables are special variables of an `ObservableObject`. If these variables change, SwiftUI will update the view accordingly. Any variables that are *not* decorated with `@Published` can change but will be ignored by SwiftUI.
4. We also add a normal variable, `private(set) var taskToEdit: Task? = nil`. When a user is attempting to *edit* a task, we need to tell the view model which task the user would like to edit. This does not need to trigger a view reload, so it's a simple variable.
5. Here's where the magic happens. As soon as the `TasksListScreenViewModel` is initialized, we need to `.observe` all the tasks by creating a live query. To prevent the `liveQuery` from being prematurely deallocated, we store it as a variable. In the observe callback, we convert all the documents into `Task` objects and set it to the `@Published tasks` variable. Every time to `.observe` fires, SwiftUI will pick up the changes and tell the view to render the list of tasks.
6. We will add an eviction call to the initializer that will remove any deleted documents from the collection
7. Add a function called `toggle()`. When a user clicks on a task's image icon, we need to trigger reversing the `isCompleted` state. In the function body we add a standard call to find the task by its `_id` and attempt to mutate the `isCompleted` property.
8. Add a function called `clickedBody`. When the user taps the `TaskRow`'s `Text` field, we need to store that task and change the `isPresentingEditScreen` to true. This will give us enough information to present a `.sheet` in the `TasksListScreenViewModel` to feed to the `EditScreen`
9. In the previous setup of the `TasksListScreen`, we added a `navigationBarItem` with a plus icon. When the user clicks this button we need to tell the view model that it should show the `EditScreen`. So we've set the `isPresentingEditScreen` property to `true`. However, because we are attempting to *create* a `Task`, we need to set the `taskToEdit` to `nil` because we don't yet have a task.

:::CodeblockTabs
TasksListScreenViewModel.swift

```swift
class TasksListScreenViewModel: ObservableObject {

    // 3.
    // highlight-start
    @Published var tasks = [Task]()
    @Published var isPresentingEditScreen: Bool = false
    // highlight-end

    // 4.
    // highlight-next-line
    private(set) var taskToEdit: Task? = nil

    let ditto: Ditto
    // 5.
    // highlight-start
    var liveQuery: DittoLiveQuery?
    var subscription: DittoSubscription?

    init(ditto: Ditto) {
        self.ditto = ditto
        self.subscription = ditto.store["tasks"].find("!isDeleted").subscribe()
        self.liveQuery = ditto.store["tasks"]
            .find("!isDeleted")
            .observeLocal(eventHandler: {  docs, _ in
                self.tasks = docs.map({ Task(document: $0) })
            })
        
        //6.
        ditto.store["tasks"].find("isDeleted == true").evict()
    }
    // highlight-end

    // 7.
    // highlight-start
    func toggle(task: Task) {
        self.ditto.store["tasks"].findByID(task._id)
            .update { mutableDoc in
                guard let mutableDoc = mutableDoc else { return }
                mutableDoc["isCompleted"].set(!mutableDoc["isCompleted"].boolValue)
            }
    }
    // highlight-end

    // 8.
    // highlight-start
    func clickedBody(task: Task) {
        taskToEdit = task
        isPresentingEditScreen = true
    }
    // highlight-end

    // 9.
    // highlight-start
    func clickedPlus() {
        taskToEdit = nil
        isPresentingEditScreen = true
    }
    // highlight-end
}
```
:::
::::

::::WorkflowBlockItem
## Render TaskRow in a ForEach within the TasksListScreen

Now we need to update our `TasksListScreen` to properly bind any callbacks, events, and data to the `TasksListScreenViewModel`.

1. Back in the `TasksListScreen` view, we need to construct our `TasksListScreenViewModel` and store it as an `@ObservedObject`. This `@ObservedObject` tells the view to watch for specific changes in the `viewModel` variable.
2. We will need to store our `ditto` object to pass to the `EditScreen` later.
3. In our `body` variable, find the `List` and add:

```swift
ForEach(viewModel.tasks) { task in
    TaskRow(task: task,
        onToggle: { task in viewModel.toggle(task: task) },
        onClickBody: { task in viewModel.clickedBody(task: task) }
    )
}
```

This will tell the list to iterate over all the `viewModel.tasks` and render a `TaskRow`. In each of the `TaskRow` children, we need to bind the `onToggle` and `onClick` callbacks to the viewModel methods.

1. Bind the plus button to the `viewModel.clickedPlus` event
2. Now we need to present a `.sheet` which will activate based on the `$viewModel.isPresentingEditScreen` variable. Notice how we added the `$` before `viewModel`. `.sheet` can edit the `isPresentingEditScreen` once it's dismissed, so we need to treat the variable as a bidirectional binding.
3. We've also included a `TasksListScreen_Previews` so that you can add some test data and see the result in a live view.

![](https://api.archbee.com/api/optimize/qoRkNxW5fJ81r_NqVpc8C/vb-n-VM9nnNxcu2o2ObeZ_image.png)

:::CodeblockTabs
TasksListScreen.swift

```swift
struct TasksListScreen: View {

    // 2.
    // highlight-next-line
    let ditto: Ditto

    // 1.
    // highlight-start
    @ObservedObject var viewModel: TasksListScreenViewModel

    init(ditto: Ditto) {
        self.ditto = ditto
        self.viewModel = TasksListScreenViewModel(ditto: ditto)
    }
    // highlight-end

    var body: some View {
        NavigationView {
            List {
                // 3.
                // highlight-start
                ForEach(viewModel.tasks) { task in
                    TaskRow(task: task,
                        onToggle: { task in viewModel.toggle(task: task) },
                        onClickBody: { task in viewModel.clickedBody(task: task) }
                    )
                }
                // highlight-end
            }
            .navigationTitle("Tasks - SwiftUI")
            .navigationBarItems(trailing: Button(action: {
                // 4
                // highlight-next-line
                viewModel.clickedPlus()
            }, label: {
                Image(systemName: "plus")
            }))
            // 5.
            // highlight-start
            .sheet(isPresented: $viewModel.isPresentingEditScreen, content: {
                EditScreen(ditto: ditto, task: viewModel.taskToEdit)
            })
            // highlight-end
        }
    }
}
// 6.
// highlight-start
struct TasksListScreen_Previews: PreviewProvider {
    static var previews: some View {
        TasksListScreen(ditto: Ditto())
    }
}
// highlight-end
```
:::

:::hint{type="info"}
Notice that we *DO NOT HAVE TO* manipulate the `tasks` value. Calling `.update` on `ditto` will automatically fire the liveQuery to update the `tasks`. You can always trust the liveQuery to immediately update the `@Published var tasks`. There is no reason to poll or force reload. Ditto will automatically handle the state changes and SwiftUI will pick these changes up automatically.
:::
::::
:::::

# Editing Tasks

Our final screen will be the **EditScreen** and its ViewModel. The **EditScreen** will be in charge of 3 functions:

- Editing an existing
- Creating a
- Deleting an existing

:::::WorkflowBlock
::::WorkflowBlockItem
## Creating the EditScreenViewModel

Like before, we need to create an `EditScreenViewModel` for the `EditScreen`. Since we've already gone over the concepts of MVVM, we will go a bit faster.

1. The `EditScreenViewModel` needs to be initialized with `ditto` and an optional `task: Task?` value. If the task value is `nil` we need to set the `canDelete` variable to `false`. This means that the user is attempting *create* a new `Task`. We will use this value to show a delete `Button` in the `EditScreen` later. We will store the `_id: String?` from the `task` parameter and use it later in the `save()` function.
2. We need two `@Published` variables to bind to a `TextField` and `Toggle` SwiftUI views for the task's `isCompleted` and `body` values. If the `task == nil`, we will set some default values like an empty string and a false `isCompleted` value.
3. When the user wants to click a save `Button`, we need to `save()` and handle either an `.upsert` or `.update` function appropriately. If the local `_id` variable is `nil`, we assume the user is attempting to create a `Task` and will call ditto's `.upsert` function. Otherwise, we will attempt to `.update` an existing task with a known `_id`.
4. Finally if a delete button is clicked, we attempt to find the document and call `.remove`

:::CodeblockTabs
EditScreenViewModel.swift

```swift
import SwiftUI
import DittoSwift

class EditScreenViewModel: ObservableObject {

    @Published var canDelete: Bool = false
    // 2.
    // highlight-start
    @Published var body: String = ""
    @Published var isCompleted: Bool = false
    // highlight-end

    // 1.
    // highlight-start
    private let _id: String?
    private let ditto: Ditto

    init(ditto: Ditto, task: Task?) {
        self._id = task?._id
        self.ditto = ditto

        canDelete = task != nil
        body = task?.body ?? ""
        isCompleted = task?.isCompleted ?? false
    }
    // highlight-end

    // 3.
    // highlight-start
    func save() {
        if let _id = _id {
            // the user is attempting to update
            ditto.store["tasks"].findByID(_id).update({ mutableDoc in
                mutableDoc?["isCompleted"].set(self.isCompleted)
                mutableDoc?["body"].set(self.body)
            })
        } else {
            // the user is attempting to upsert
            try! ditto.store["tasks"].upsert([
                "body": body,
                "isCompleted": isCompleted,
                "isDeleted": false
            ])
        }
    }
    // highlight-end

    // 4.
    // highlight-start
    func delete() {
        guard let _id = _id else { return }
        ditto.store["tasks"].findByID(_id).update { doc in
            doc?["isDeleted"].set(true)
        }
    }
    // highlight-end
}
```
:::
::::

::::WorkflowBlockItem
## Create the EditScreen

Like the `TasksListScreen.swift` in the previous section, we will create an `EditScreen.swift`.
This screen will use SwiftUI's Form and Section wrapper.

1. An `TextField` which we use to edit the `Task.body`
2. A `Switch` which is used to edit the `Task.isCompleted`
3. A `Button` for saving a task.
4. A `Button` for deleting a task

![](https://api.archbee.com/api/optimize/qoRkNxW5fJ81r_NqVpc8C/qxnw8uO3LAHZsyqJjXLdh_image.png)

1. In the `EditScreen` we need to add a `@Environment(\.presentationMode) private var presentationMode`. In SwiftUI views house some environment variables. Because the `TasksListScreen` presened the `EditScreen` as a `.sheet`, we need a way to dismiss the current screen if the user taps any of the buttons. To learn more about `Environment`, [read Apple's official documentation.](https://developer.apple.com/documentation/swiftui/environment). To dismiss the current screen we can call `self.presentationMode.wrappedValue.dismiss()`
2. Like before, store the `EditScreenViewModel` as an `ObservedObject`. Pass the `task: Task?` and the `ditto` instance to properly initialize the `EditScreenViewModel`. Now the ViewModel should know if the user is attempting a creation or update flow.
3. We now can bind the `TextField` for the `$viewModel.body` and `Toggle` to the `$viewModel.isCompleted`. Notice the `$`, this allows SwiftUI fields to bi-directionally edit these `@Published` values and trigger efficient view reloading.
4. Bind the save button's `action:` handler to the `viewModel.save()` function and dismiss the view. Whenever the user clicks the save button, they will save the current data and return back to the `TasksListScreen`
5. If the `viewModel.canDelete` is `true`, we can show a delete `Button`. Notice how we don't need the `$` since we are only reading the value once. Moreover, we do not need to tell SwiftUI to re-render on `canDelete` since it will never change during the `EditScreen`'s life cycle.
6. Bind the delete button's `action:` to the `viewModel.delete()` function and dismiss the view.
7. Finally we add a `EditScreen_Previews` so that you can easily watch the view's final rendering as you develop.

:::CodeblockTabs
EditScreen.swift

```swift
struct EditScreen: View {

    // 1.
    // highlight-next-line
    @Environment(\.presentationMode) private var presentationMode

    // 2.
    // highlight-start
    @ObservedObject var viewModel: EditScreenViewModel

    init(ditto: Ditto, task: Task?) {
        viewModel = EditScreenViewModel(ditto: ditto, task: task)
    }
    // highlight-end

    var body: some View {
        NavigationView {
            Form {
                Section {
                    // 3.
                    // highlight-start
                    TextField("Body", text: $viewModel.body)
                    Toggle("Is Completed", isOn: $viewModel.isCompleted)
                    // highlight-end
                }
                Section {
                    Button(action: {
                        // 4.
                        // highlight-start
                        viewModel.save()
                        self.presentationMode.wrappedValue.dismiss()
                        // highlight-end
                    }, label: {
                        Text(viewModel.canDelete ? "Save" : "Create")
                    })
                }
                // 5.
                // highlight-next-line
                if viewModel.canDelete {
                    Section {
                        Button(action: {
                            // 6.
                            // highlight-start
                            viewModel.delete()
                            self.presentationMode.wrappedValue.dismiss()
                            // highlight-end
                        }, label: {
                            Text("Delete")
                                .foregroundColor(.red)
                        })
                    }
                }
            }
            .navigationTitle(viewModel.canDelete ? "Edit Task": "Create Task")
            .navigationBarItems(trailing: Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }, label: {
                Text("Cancel")
            }))
        }
    }
}

// 7.
// highlight-start
struct EditScreen_Previews: PreviewProvider {
    static var previews: some View {
        EditScreen(ditto: Ditto(), task: Task(body: "Get Milk", isCompleted: true))
    }
}
// highlight-end
```
:::
::::

:::WorkflowBlockItem
## Run the App\![​](https://docs.ditto.live/ios/tutorial/swift/edit-screen#4-4-run-the-app)

Congratulations you have successfully created a task app using Ditto!&#x20;
:::
:::::

