Implementing Renaming and Reordering in SwiftUI Sidebar for macOS App

I'm trying to implement a sidebar similar to those in native Apple apps like Finder or Notes, where items can be rearranged and renamed.

struct SidebarListView: View {
  var items: [Item] // class using SwiftData

  var body: some View {
    List {
      ForEach(self.items) { item in
        ItemView(item: item)
      }
      .onMove(perform: self.moveItem)
    }
  }

  // Additional code...
}
struct ItemView: View {
  var item: Item

  @State private var newTitle: String = ""

  var body: some View {
    NavigationLink {
      Text(self.item.title)
    } label: {
      Label {
        TextField(text: self.$newTitle) {}
          .onSubmit(self.renameItem)
      } icon: { Image(systemName: "folder") }
    }
  }

  // Additional code...
}

In my sidebar, each item is a NavigationLink containing a TextField. I've managed to replicate the following behaviors seen in native apps:

  1. When an item is active, clicking on the item name once allows it to be renamed.
  2. When an item is active, pressing the Return key allows it to be renamed.

However, after rearranging the items, the ability to rename an item with the Return key (behavior 2) stops working.

Given that these are fundamental UI behaviors in Mac apps, I believe they should be straightforward to implement in SwiftUI. However, I couldn't find any official documentation on placing a TextField inside a NavigationLink and having it focus with the Return key (even before rearranging items).

Does anyone have any insights or suggestions on this issue?

Answered by szymczyk in 776343022

This could be a SwiftUI bug on Mac. I recommend filing a bug report. Choose Help > Report an Issue in Xcode to file a bug report.

Please provide more details on "the ability to rename an item with the Return key (behavior 2) stops working". What exactly happens when you select a list item and press the Return key?

What are you using with your navigation links: NavigationSplitView, NavigationStack, or NavigationView?

Show your code for the functions moveItem and renameItem.

I tested some code in an app of mine that lets people rearrange and rename list items, and I could not replicate the behavior you see. I was able to select an item, press the Return key, and rename the item.

My code uses NavigationView because my app must support macOS 12. My code for the navigation link binds the text field to the selected item, the item property in your item view, instead of using a separate property, such as your newTitle property.

Thank you for your response.

For now, I want to confirm that it works on the latest macOS, so I am using NavigationSplitView. Additionally, pressing the Return key plays the Alert Sound (the default macOS setting is Funky).

Here is all the code (I've omitted the item renaming function as it doesn't seem relevant to this behavior).

import SwiftUI
import SwiftData

@Model
final class Item {
  let id = UUID()
  var title: String
  var index: Int

  init(title: String, index: Int) {
    self.title = title
    self.index = index
  }
}

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup { ContentView() }
      .modelContainer(for: Item.self)
  }
}

struct ContentView: View {
  @Environment(\.modelContext) private var modelContext
  @Query(sort: \Item.index, order: .forward) private var items: [Item]

  var body: some View {
    NavigationSplitView {
      List {
        ForEach(self.items) { item in
          NavigationLink { Text(item.title) } label: { ItemView(item: item) }
        }
        .onMove(perform: self.moveItem)
      }
      .navigationSplitViewColumnWidth(min: 180, ideal: 200)
      .toolbar {
        ToolbarItem {
          Button(action: addItem) { Label("Add Item", systemImage: "plus") }
        }
      }
    } detail: {
      Text("Select an item")
    }
  }

  private func addItem() {
    let newItem = Item(title: "New Title \(self.items.count)", index: self.items.count)

    self.modelContext.insert(newItem)
  }
  private func moveItem(sources: IndexSet, destination: Int) {
    var newItems = self.items
    newItems.move(fromOffsets: sources, toOffset: destination)
    newItems.enumerated().forEach { index, item in item.index = index }

    try! self.modelContext.save()
  }
}

struct ItemView: View {
  @Bindable var item: Item

  var body: some View {
    NavigationLink {
      Text(self.item.id.uuidString)
    } label: {
      TextField(text: self.$item.title) {}
    }
  }
}

#Preview {
  ContentView().modelContainer(for: Item.self, inMemory: true)
}

Additionally, I'm attaching a video that shows the behavior immediately after clicking on each item to activate it and then pressing the Return key.

https://imgur.com/zeVkeGo

Accepted Answer

This could be a SwiftUI bug on Mac. I recommend filing a bug report. Choose Help > Report an Issue in Xcode to file a bug report.

Implementing Renaming and Reordering in SwiftUI Sidebar for macOS App
 
 
Q