20

UPDATE: 14 months later, there is this intriguing note in the AppKit release notes:

A TextField that you are editing inside a selected List row now has correct text foreground colors. (68545878)

Now when placing a TextField in a List, the TextField becomes focused on click the very first time it is selected, but subsequent editing attempts fail: the List row is selected but the TextField does not gain focus.

O/P:

In a beta6 SwiftUI macOS (not iOS) app, I need to have a List with editable text fields, but the TextFields in the list are not editable. It works fine if I swap out List for Form (or just VStack), but I need it working with a List. Is there some trick to tell the list to make the fields editable?

import SwiftUI

struct ContentView: View {
    @State var stringField: String = ""

    var body: some View {
        List { // works if this is VStack or Form
            Section(header: Text("Form Header"), footer: Text("Form Footer")) {
                TextField("Line 1", text: $stringField).environment(\.isEnabled, true)
                TextField("Line 2", text: $stringField)
                TextField("Line 3", text: $stringField)
                TextField("Line 4", text: $stringField)
                TextField("Line 5", text: $stringField)
            }
        }
    }
}
marcprux
  • 9,845
  • 3
  • 55
  • 72
  • 1
    Could you explain why you would want to edit the same variable in 5 textfields please. – Michael Salmon Aug 30 '19 at 18:21
  • I don't know what it's supposed to do but if I cut your code and paste it into Xcode and run it in the simulator then I can edit the text in all textfields and the edited text shows up in all textfields. It doesn't seem to work in preview but then so little works in preview that I hardly ever use it. – Michael Salmon Aug 31 '19 at 06:07
  • 1
    I know it works in the simulator, but as stated, this is for a macOS app, not an iOS app. The bindings to the same string field is just an example – it could just as easily be 5 different bindings and the text fields would remain un-editable on macOS. – marcprux Aug 31 '19 at 11:47
  • Sorry for missing that it was a macOS app although it seems to me that SwiftUI is only available for catalyst on the mac. Anyway it still works for me, ugly as sin but functional. Maybe they fixed it in Beta 7. – Michael Salmon Aug 31 '19 at 12:35
  • It's not catalyst (UIKit), not iOS (UIKit), but an actual macOS app (AppKit, for which SwiftUI is implemented). If you run the example as a macOS app, the fields will be un-editable, even in beta7. – marcprux Aug 31 '19 at 12:58
  • 1
    Looks like you found a bug. For what it's worth `ForEach(1...5, id: \.self) { TextField("Line \($0)", text: self.$stringField) }` works, even after wrapping it in a ScrollView() – Michael Salmon Aug 31 '19 at 14:13
  • I also came across this issue. Ended up wrapping in a ScrollView instead of a List. – Cortado-J Nov 22 '19 at 02:02

5 Answers5

5

Bug Report

I updated the project to target a MacOS app and found the bug you are reporting. I've updated Apple with this feedback because it indeed does seem to be a bug (Welcome to Beta).

FB7174245 - SwiftUI Textfields embedded in a List are not editable when target is macOS

Update

So what's the point of all the focus on state and binding below? One variable should be bound to a single control. Here is a working example. I'll leave up the older answer as it carries the example forward with a full app saving and retrieving data to/from CoreData.

import SwiftUI

struct ContentView: View {
    @State var field1: String = ""
    @State var field2: String = ""
    @State var field3: String = ""
    @State var field4: String = ""
    @State var field5: String = ""

    var body: some View {
        List { // works if this is VStack or Form
            Section(header: Text("Form Header"), footer: Text("Form Footer")) {
                TextField("Line 1", text: $field1).environment(\.isEnabled, true)
                TextField("Line 2", text: $field2)
                TextField("Line 3", text: $field3)
                TextField("Line 4", text: $field4)
                TextField("Line 5", text: $field5)
            }
        }
    }
}

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

BTW - This works on Xcode Beta (11M392r) with MacOS Catalina - 10.15 Beta (19A546d).

Sample Project

Check out this sample that includes an editable Textfield that writes to CoreData, which I am building on Github.

Take a look at the difference between @State and @Binding so that you can manipulate data from outside of the content view. (60-sec video)

struct ContentView: View {
    // 1.
    @State var name: String = ""
    var body: some View {
        VStack {
            // 2.
            TextField(" Enter some text", text: $name)
                .border(Color.black)
            Text("Text entered:")
            // 3.
            Text("\(name)")
        }
        .padding()
        .font(.title)
    }
}

source

Check out the following answer for the appropriate use-case of @State (SO Answer)

marshallino16
  • 2,645
  • 15
  • 29
Tommie C.
  • 12,895
  • 5
  • 82
  • 100
  • The bindings to the state work fine. This isn't a question about how @Bindings/@State work, but rather a question about why a TextField is un-editable when contained in a List on macOS. – marcprux Aug 31 '19 at 11:50
  • This is not an answer. The working example does not work! – Damiaan Dufaux Jan 21 '20 at 13:56
  • Hello, I have tried to fix the issue wrapping NSTextField, however it seems that any view contained in List on macOS is not editable. This is a the most serious bug ever on macOS regarding SwiftUI – Jan Kubny Feb 14 '20 at 14:53
  • @JanKubny - You may want to use the Feedback Assistant and the reference number in this message so that Apple gives this more attention. – Tommie C. Feb 14 '20 at 14:57
4

The following code is only an experiment to understand the character of List in SwiftUI and show an alternative. What I understand from observing the output from various combinations is that, List View's style is structured in a way to override the default behaviors of underlying View to become Selectable. This means that TextField does absolutely different. TextField is an focusable element where we can type. This focusing variable is not wired in to List View to work together. Hence, List override default focusable. Hence it is not possible to create List with TextView. But if you need, next best option is ScrollView instead of List and do the styling explicitly. Check the following code and both ways.

import SwiftUI

struct ContentView: View {
    @State private var arr = ["1","2","3"]
    var body: some View {
        HStack {
            VStack {
                List {
                    ForEach(self.arr.indices, id:\.self) {
                        TextField("", text: self.$arr[$0])
                    }
                }
            }
            .frame(minWidth: 150, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
            VStack {
                ScrollView {
                    ForEach(self.arr.indices, id:\.self) {
                        TextField("", text: self.$arr[$0])
                        .textFieldStyle(PlainTextFieldStyle())
                        .padding(2)
                    }
                }
                .padding(.leading, 5)
                .padding(3)
            }
            .background(Color(NSColor.alternatingContentBackgroundColors[0]))
            .frame(minWidth: 150, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
        }
    }
}

extension NSTextField {
    open override var focusRingType: NSFocusRingType {
        get { .none }
        set { }
    }
}
NikzJon
  • 912
  • 7
  • 25
  • It is 2022 and I've not had any troubles with your first list at all, but ForEach(self.arr.indices, id:\.self) was exactly what I needed, because I've been struggling to work with a more complex model, and there is no path from ForEach(itemArray) to item.name. Accessing by index is the way to go here. – green_knight Sep 16 '22 at 07:43
1

Does get focus—if you tap just right

Using Xcode 12.4 and SwiftUI 2 for a macOS app, I seemed to have the same problem: Could not make TextEdit work inside a List. After reading here, and experimenting some more, I realized that in my case the TextField does reliably get the focus, but only if you tap in just the right way. I think and hope this is not how it should be working, so I posted the question TextField inside a List in SwiftUI on macOS: Editing not working well, explaining the details of my observations.

In summary: Single-tap exactly on existing text does give focus (after a small, annoying, delay). Double-tap anywhere does not give focus. Single-tap anywhere in an empty field does give focus.

rene
  • 1,975
  • 21
  • 33
0

SwiftUI 2.0

You can use TextEditor instead of TextField in lists in order to get editable text fields.

TextEditor(text: $strings)
    .font(.body)
    .cornerRadius(5)
    .shadow(color: Color.black.opacity(0.18), radius: 0.8, y: 1)
    .frame(height: 20)

This way you will get a similar aspect between the TextEditor and TextField.

AlbertUI
  • 1,409
  • 9
  • 26
  • 1
    This does technically work, but `TextEditor` isn't a good substitute for `TextField`: there's no support for formatters, and there isn't any way to disable multi-line editing or enable tab-to-move-fields. – marcprux Sep 30 '20 at 15:24
0

A TextField placed inside a SwiftUI List (with selection enabled) appears to have the proper editing behavior as of macOS 13.5.1. When you click on a selected row, the TextField becomes editable. Horizontal "scrolling" for long text that clips also works properly. These features did NOT work properly on macOS 12.6.8.

However, ContextMenu doesn't behave correctly. Right-clicking (e.g. two finger tap on a trackpad) on a selected TextField does not bring up the context menu attached to the TextField (or List), but instead it incorrectly makes the TextField editable and brings up the system text selection menu for the selected text inside the TextField (e.g. Lookup, Translate, Search, Cut, Copy, Paste, Share, etc.). Control-clicking on the TextField does not bring up any menu. Instead, if the TextField was selected, it puts it into editing mode. If a non-selected row/TextField was control-clicked, it selects that TextField instead of showing the ContextMenu. It appears List does not support something analogous to NSTableView's clickedRow of where it is separate from the selected row(s) and allows you to preserve row selection while bringing up a context menu for the clicked row.

It feels like the engineers working on SwiftUI don't have much experience with how macOS should behave.

Andrew
  • 7,630
  • 3
  • 42
  • 51