Dree
Dree

Reputation: 722

Initializers with different stored properties

I have the following view where I pass a binding to an item that I need to be selected.

struct SelectionListView<Data>: View where Data: RandomAccessCollection, Data.Element: Identifiable, Data.Element: Named {

    private let data: Data
    @Binding private var isPresented: Bool
    @Binding private var selectedElement: Data.Element

    init(
        data: Data,
        selectedElement: Binding<Data.Element>,
        isPresented: Binding<Bool>
    ) {
        self.data = data
        _selectedElement = selectedElement
        _isPresented = isPresented
    }
    
    var body: some View {
        VStack {
            ForEach(data) { element in
                Button(element.name) {
                    selectedElement = element
                    isPresented.toggle()
                }
                .foregroundColor(
                    selectedElement.id == item.id
                        ? .black
                        : .white
                )
            }
        }
    }
}

I would need a slightly different initializer of this view where I can only pass the element ID, instead of the whole element. I'm having trouble achieving this solution. To make it even more clear, it would be great if I could have a second initializer such that:

init(
    data: Data,
    selectedId: Binding<Data.Element.ID>,
    isPresented: Binding<Bool>
)

Upvotes: 0

Views: 53

Answers (2)

Mihai Fratu
Mihai Fratu

Reputation: 7663

I'm not really sure what you are trying to achieve. Something feels off :) But anyway, here's a variant of your code that would do what you want:

struct SelectionListView<Data>: View where Data: RandomAccessCollection, Data.Element: Identifiable, Data.Element: Named {

    private let data: Data
    @Binding private var isPresented: Bool
    @Binding private var selectedElement: Data.Element
    @Binding private var selectedId: Data.Element.ID
    
    init(
        data: Data,
        selectedElement: Binding<Data.Element>,
        isPresented: Binding<Bool>
    ) {
        self.data = data
        _selectedElement = selectedElement
        _selectedId = .constant(selectedElement.wrappedValue.id)
        _isPresented = isPresented
    }
    
    init(
        data: Data,
        selectedId: Binding<Data.Element.ID>,
        isPresented: Binding<Bool>
    ) {
        self.data = data
        _selectedElement = .constant(data.first(where: { $0.id == selectedId.wrappedValue })!)
        _selectedId = selectedId
        _isPresented = isPresented
    }
    
    var body: some View {
        VStack {
            ForEach(data) { element in
                Button(element.name) {
                    selectedElement = element
                    selectedId = element.id
                    isPresented.toggle()
                }
                .foregroundColor(
                    selectedElement.id == element.id
                        ? .black
                        : .gray
                )
            }
        }
    }
}

Upvotes: 0

George
George

Reputation: 30421

Here is a working version. I decided to store the element or id in their own enum cases. I made the view separate just so it is a little easier to understand what I did.

Working code:

struct SelectionListView<Data>: View where Data: RandomAccessCollection, Data.Element: Identifiable, Data.Element: Named {
    
    enum Selected {
        case element(Binding<Data.Element>)
        case id(Binding<Data.Element.ID>)
    }
    
    @Binding private var isPresented: Bool
    private let data: Data
    private let selected: Selected
    
    init(
        data: Data,
        selectedElement: Binding<Data.Element>,
        isPresented: Binding<Bool>
    ) {
        self.data = data
        selected = .element(selectedElement)
        _isPresented = isPresented
    }
    
    init(
        data: Data,
        selectedId: Binding<Data.Element.ID>,
        isPresented: Binding<Bool>
    ) {
        self.data = data
        selected = .id(selectedId)
        _isPresented = isPresented
    }
    
    var body: some View {
        SelectionListItem(data: data) { dataElement in
            switch selected {
            case .element(let element):
                element.wrappedValue = dataElement
                print("Selected element:", element.wrappedValue)
            case .id(let id):
                id.wrappedValue = dataElement.id
                print("Selected element ID:", id.wrappedValue)
            }
            
            isPresented.toggle()
        }
    }
}


struct SelectionListItem<Data>: View where Data: RandomAccessCollection, Data.Element: Identifiable, Data.Element: Named {
    let data: Data
    let action: (Data.Element) -> Void
    
    var body: some View {
        VStack {
            ForEach(data) { element in
                Button(element.name) {
                    action(element)
                }
                .foregroundColor(
                    .red  // Temporary because I don't know what `item.id` is

//                    selectedElement.id == item.id
//                        ? .black
//                        : .white
                )
            }
        }
    }
}

Other code for minimal working example:

struct ContentView: View {
    
    @State private var selection: StrItem
    @State private var selectionId: StrItem.ID
    @State private var isPresented = true
    private let data: [StrItem]
    
    init() {
        data = [StrItem("Hello"), StrItem("world!")]
        _selection = State(initialValue: data.first!)
        _selectionId = State(initialValue: data.first!.id)
    }
    
    var body: some View {
        // Comment these to try each initializer

        //SelectionListView(data: data, selectedElement: $selection, isPresented: $isPresented)
        SelectionListView(data: data, selectedId: $selectionId, isPresented: $isPresented)
    }
}

protocol Named {
    var name: String { get }
}

struct StrItem: Identifiable, Named {
    let id = UUID()
    let str: String
    var name: String { id.uuidString }
    
    init(_ str: String) {
        self.str = str
    }
}

Upvotes: 1

Related Questions