Daniel
Daniel

Reputation: 436

List selection as Set<String> - how to use?

Am playing around with SwiftUI and am obviously not getting it.

Basic example which works and is just displaying the selected name.

struct ContentView: View {
    let names = ["Joe", "Jim", "Paul"]

    @State var selectedName = Set<String>()

    var body: some View {
        VStack {
            List(names, id: \.self, selection: $selectedName) { name in
                Text(name)
            }
            if !selectedName.isEmpty {
                Text(selectedName.first!) // <-- this line
            }
        }
    }
}

What I want is a textfield where that name can be changed. Tried many ways but getting another error every time.

TextField("Name", text: $selectedName)

Gives this error: Cannot convert value of type 'Binding<Set<String>>' to expected argument type 'Binding<String>'

TextField("Name", text: $selectedName.first!)

Cannot force unwrap value of non-optional type 'Binding<((String) throws -> Bool) throws -> String?>'

How would I do this?

Upvotes: 0

Views: 1493

Answers (3)

Asperi
Asperi

Reputation: 258345

Ok, here is my alternate if I'd needed to edit some value of names having in one screen and list and edit field and make them all synchronised and not confuse each other.

Here is full testable module (tested on Xcode 11.2/iOS 13.2). As I tested it for iOS there are API requirement for put List into EditMode to process selection, so this included.

struct TestChangeSelectedItem: View {
    @State var names = ["Joe", "Jim", "Paul"] // made modifiable
    @State var selectedName: String? = nil // only one can be edited, so single selection

    @State var editMode: EditMode = .active // Tested for iOS, so it is needed

    var body: some View {
        VStack {
            List(selection: $selectedName) {
                ForEach(names, id: \.self) { name in
                    Text(name)
                }
            }
            .environment(\.editMode, $editMode) // Tested for iOS, so it is needed

            if selectedName != nil {
                Divider()
                Text(selectedName!) // Left to see updates for selection
                editor(for: selectedName!) // Separated to make more clear
            }
        }
    }

    private func editor(for selection: String) -> some View {
        let index = names.firstIndex(of: selection)!
        var editedValue = selection // local to avoid cycling in refresh

        return HStack {
            Text("New name:")
            TextField("Name", text: Binding<String>(get: { editedValue }, set: { editedValue = $0}), onCommit: {
                self.names[index] = editedValue
                self.selectedName = editedValue
            })
        }
    }
}

struct TestChangeSelectedItem_Previews: PreviewProvider {
    static var previews: some View {
        TestChangeSelectedItem()
    }
}

Upvotes: 0

E.Coms
E.Coms

Reputation: 11539

You may make a binding by yourself:

 TextField("Name", text: Binding<String>(get: {self.selectedName.first!}, set: { _ in}) )

Upvotes: 1

FRIDDAY
FRIDDAY

Reputation: 4169

Obviously you can't pass Binding<Set<String>> to Binding<String>. Here gives you an idea or solution to change selectedName variable using TextField:

I added a new variable which is Binding<String>. Then I change the selectedName inside the TextField's onCommit closure.

struct ContentView: View {

let names = ["Joe", "Jim", "Paul"]

@State var selectedName = Set<String>()
@State var textFieldName = ""

var body: some View {
    VStack {
        List(names, id: \.self, selection: $selectedName) { name in
            Text(name)
        }
        if !selectedName.isEmpty {
            Text(selectedName.first!)
        }

        Text(textFieldName)

        TextField("Name", text: $textFieldName, onEditingChanged: { (Bool) in
            //onEditing
        }) {
            //onCommit
            self.selectedName.insert(self.textFieldName)
        }
    }
  }
}

Upvotes: 0

Related Questions