Using SwiftUI @FocusState

Apple’s Reminders app reveals additional editing controls based on the user’s current focus. I wanted to reproduce this behaviour in SwiftUI.

Note the cursor placement and the visibility of the Note the cursor placement and the visibility of the "Add Date" and "Add Location" controls

I will start with a List of Task models.

TaskList.swift:

List {
  ForEach(tasks) { task in
    TaskView(task: task)
}

Then I’ll set up TaskView with some state for each of the Task model’s fields
TaskView.swift:

struct TaskView: View {    
    var task:Task    

    @State private var title:String
    @State private var notes:String

    init(task: Task) {
        self.task = task
        _title = .init(initialValue: task.title ?? "")
        _notes = .init(initialValue: task.notes ?? "")
    }

    var body: some View {
        HStack{
            VStack(alignment: .leading){
                TextField("Title", text: $title)
                    .foregroundStyle(.primary)

                Text((try? AttributedString(markdown: notes)) ?? "")
                    .foregroundStyle(.secondary)               
            }
        }
        .padding()
    }
}

This is enough to make each task editable (I’ll skip the storage part today).

Note that I’m using Swift’s new Markdown support for my notes field, using AttributedString(markdown: notes).

I’m also using the new foregroundStyle .primary and .secondary options for my text formatting instead of using plain colour values.

Our task list with some test data Our task list with some test data

Then we just create a private enum called Field and an accompanying @FocusState variable as per this video.

private enum Field: Int, Hashable{
    case title, notes
}
@FocusState private var focusedField: Field?

Now we have a focusedField variable that we can look at for the current focus state. This lets us dynamically toggle form controls when any field in a particular TaskView has focus.

VStack(alignment: .leading){
    TextField("Title", text: $title)
        .foregroundStyle(.primary)
        .focused($focusedField, equals: .title)

    if focusedField == nil {
        Text((try? AttributedString(markdown: notes)) ?? "")
            .foregroundStyle(.secondary)
    }else{
        TextField("Notes", text: $notes)
            .focused($focusedField, equals: .notes)
        HStack{
            Button(action: {}){
                Text("Add Location")
            }.buttonStyle(.bordered)
        }
    }
}

Now when one of the fields is focused, the “Add Location” button is visible.

When row has focus, “Add Location” button is visible When row has focus, “Add Location” button is visible

And our raw markdown is only displayed when our row is selected And our raw markdown is only displayed when our row is selected

I’m not sure this would have been possible in previous versions of SwiftUI without using a UIKit control wrapper! @FocusState is a welcome new feature.

Next step, figure out how to focus a row in the list when it is first added…

Get your free book

Sign up for my newsletter to get weekly tips on mood tracking, happiness data analysis and forming better habits.

I will send you a copy of my eBook about mood tracking.

Find out more

*
indicates required