0

I have trouble with getting Binding values in correct format out of dataModel.

The XCode error I am getting is "Cannot convert value of type '[Binding<String?>]' to expected argument type 'Binding<[String]>'"

public final class RoomSelectionDataModel: ObservableObject {
    @Published var roomsList: [Room]
    @Published var selectedRoom: String?

}
public struct RoomSelectionView: View {
    @StateObject var dataModel: RoomSelectionDataModel
    public var body: some View {
        let bindingArray = $dataModel.roomsList.compactMap { $0.name } // here the format is '[Binding<String?>]' instead of 'Binding<[String]>'
        SelectionList(listElements: bindingArray,
                      backgroundColor: Color(red: 0.94, green: 0.94, blue: 0.94), // color to be removed
                      selectedElement: $dataModel.selectedRoom)
    }
    
    public init(dataModel: RoomSelectionDataModel) {
        self._dataModel = StateObject(wrappedValue: dataModel)
        
    }
}
public struct SelectionList: View {
    
    let backgroundColor: Color
    @Binding var listElements: [String]
    @Binding var selectedElement: String?
    
    public init(listElements: Binding<[String]>, backgroundColor: Color, selectedElement: Binding<String?>) {
        self._listElements = listElements
        self.backgroundColor = backgroundColor
        self._selectedElement = selectedElement
    }
}
 public class Room: NSObject, Codable {
    public var name: String?
}

The models are simplified here, to only have the relevant information.

Adam Linke
  • 81
  • 6
  • What's the definition of `SelectionList`? But, the error is saying that you are giving an array of bindings optional string, but it expects an binding of array of string. – Larme May 17 '22 at 08:25
  • Also, show the declaration of `Room` – Paulw11 May 17 '22 at 08:28
  • Updated post with requested information. – Adam Linke May 17 '22 at 08:47
  • You seem to have mismatched types. Why are you trying to connect an array of bindings to a binding of an array? – Cristik May 17 '22 at 09:34
  • @burnsi Yes I modify it. – Adam Linke May 17 '22 at 10:22
  • @Cristik This is the essence of my question. I could add another property like ```@Published var roomsListAsString: [String] = [] ``` And change the implementation of ```@Published var roomsList: [Room]``` to ```@Published var roomsList: [Room] { didSet { self. roomsListAsString = roomsList.compactMap { $0.name } } } ``` But this doesn't feel clean, and I think this is like second unecessary source of truth(which is redundant). So the question is basically how to do it in a clean way, having one source of truth. – Adam Linke May 17 '22 at 10:30
  • @Asperi Could you please specify which local variable do you mean? – Adam Linke May 17 '22 at 10:37
  • Still, it's clear that you are mixing two incompatible types. Why would you expect for that to work? Maybe rephrase the question to let us know what you need to achieve? – Cristik May 17 '22 at 11:06
  • @Cristik "Why would you expect for that to work? ". I am not convinced these are incompatible types. What I am using(without the compactMap function) is basically Binding type while what I need is Binding But every single room has property of String type. So it feels like it should be possible to achieve something like Binding which would be Binding. Or other approach could be to flatten it like with Combine Publishers, where you can flatMap them into one, but I am not sure if that's possibile with SwiftUI Binding types. – Adam Linke May 17 '22 at 11:25
  • But they are obviously incompatible, the error message clearly states this: cannot convert from `[Binding']` to`Binding<[String]>`. Leaving aside the optionality, you're still trying to pass an array of bindings instead of a binding to an array. – Cristik May 17 '22 at 11:27
  • 1
    `Binding` is by definition a two-way connection. You break the connection with the `compactMap` line, its purpose is to transform. Swift does not have the ability to maintain the connection between the array of names and the array of rooms. Pass the entire room array if you need the connection or you can keep the array of strings without the binding, solely for display. If you extract the `Room` object with a `ForEach` you can use `room.name`. – lorem ipsum May 17 '22 at 12:16

1 Answers1

0

Lets try explicitly creating the binding array as it seems you wish to:

        let bindingArray = Binding<[String]>(
            get: { dataModel.roomsList.compactMap { $0.name } },
            set: { newValue in
                let rooms = dataModel.roomsList.filter { $0.name != nil }
                rooms.enumerated().forEach { numberedPair in
                    numberedPair.element.name = newValue[numberedPair.offset]
                }
            })

The get side of the binding is pretty straightforward. When you do the put side, things get a little more complicated. What my code does is assumes that the rooms are in the same order, that all the rooms which had names last time still have names, and that you're going to assign the names into the rooms that have them in their ordinal positions based on the new array.

All in all it's not clear why you want a Binding on the array because the put operation of [String] doesn't really match the model here.

Scott Thompson
  • 22,629
  • 4
  • 32
  • 34