-3

UPDATE: Let's put it very simple. This:

struct FavoriteButton: View {
    
    @Binding var items : [Item]
    
    var body: some View {
        Button("Toggle") {
              .....
        }
    }
}

You have the item at your disposal, not the array to pass in FavoriteButton(items: [something]). If you just create the array it will break the binding as it is the struct not the class and it will not keep the reference. Can you pass it somehow and keep the binding?

ORIGINAL QUESTION (which also explains why):

I have a List in SwiftUI. Let's say that it is bound to [Item] array and that there is a property Item.isFavorite that triggers visual change in a row. The item should be handled by the FavoriteButton that can be triggered by the single item like in swipeActions or by multiple items like in contextMenu(forSelection:).

The question is whether it is possible to do this by passing an item array to the button? My problem is that array needs to be passed as the binding so that changing of the isFavorite updates the view, and if I create a new array from the single item I seem to be unable to keep the binding.

One possible solution is to pass the item id array and then do the work in the view model (this way I don't need to keep the binding in the button). However I am particularly interested in whether it is possible to be done by passing the item array to the button and binding. I am aware that the solution with just using ids and view model might be better, but for some curiosity I am interested if this is possible (I think it should be).

EDIT: as some people asked for clarification, I'll copy it from my comment below where I have provided it:

You have a FavoriteButton view that has @Binding var items : [Item]. You only have Item (not an array) when creating the FavoriteButton at your disposal. How do you pass it as the array that keeps the binding to the original item?

Ivan Ičin
  • 9,672
  • 5
  • 36
  • 57
  • 2
    Sure, it sounds possible, but without any code to go on, there's not really an efficient way to provide an answer. – jnpdx Jan 09 '23 at 21:37
  • 2
    It sounds like work that should be done in the model regardless, but yes, you need to show some code – Paulw11 Jan 09 '23 at 21:41
  • @jnpdx thanks for your reply. I can, but it really comes down to several things written above. You have a FavoriteButton View that has [at]Binding var items : [Item]. You only have Item (not an array) when creating the FavoriteButton at your disposal. How do you pass it as the array that keeps the binding to the original item? – Ivan Ičin Jan 09 '23 at 21:52
  • 2
    How are you using the context menu? You have the array of selected items there, so you pass that to your model. In the case of a single item, you can pass `[item]` to the model function. – Paulw11 Jan 09 '23 at 21:55
  • @Paulw11 that particular variant contextMenu(forSelection:) is a new one in iOS 16 and macOS 13 and it passes the [Item] to enclosure. – Ivan Ičin Jan 09 '23 at 21:57
  • I think you are missing the model from your view - The `FavouriteButton` should pass the `[item]` to the model and it should be responsible for changing the favourite state. – Paulw11 Jan 10 '23 at 00:31
  • Also, I am assuming that `Item` is a struct, not a class. – Paulw11 Jan 10 '23 at 00:38
  • @Paulw11 I agree that it is possible to do this with view model (though I would pass them only ids). However it is a legitimate question to ask if it is possible without view model. It is also a legitimate answer that it is impossible if you consider that as final answer. It would be also possible to have two FavoriteButtons that handle different cases, but it is challenging to make only one and make it work. – Ivan Ičin Jan 10 '23 at 00:41
  • The problem you have with not using a view model is one of immutability (assuming that `Item`is a struct). You can't mutate the array and you can't mutate the item. This practically necessitates a view model. If `Item` is a class then you can do it without a view model. – Paulw11 Jan 10 '23 at 00:44
  • @Paulw11 clearly alternatives are possible, I am aware of them, as said you can also have two buttons for both cases as alternative. Feel free to answer that it is impossible, I’ll wait some more and mark it as the answer as I am also not aware on how to do it, but then I am somewhat new to Swift and felt like that there may be some things that I didn’t know. – Ivan Ičin Jan 10 '23 at 00:47
  • 4
    This is why we keep asking for code; what does the view that contains the favourite button look like? Is it using @State to store the items? If so, then that is your fundamental problem; you need a proper model. As I said your fundamental issue is immutability – Paulw11 Jan 10 '23 at 01:08
  • @PaulW I gave the exactly needed code for this question. As said I don’t want any alternative suggestions. That is a legitimate request. I understand that you want to help in a more broad way and that is noble, but then I *really* haven’t asked for that. – Ivan Ičin Jan 10 '23 at 01:46
  • 3
    But you haven't provided the required code. The context from which a binding is provided is crucial to the issue. You may be able to easily solve the problem with a custom binding being passed to your button. The button view in isolation doesn't adequately describe the issue. You need to provide an [mcve] – Paulw11 Jan 10 '23 at 01:57
  • 4
    It's not bullying. I asked repeatedly for you to show how the data is managed outside of the button view since I believe that this important to understand what you are trying to do. You say you are new to Swift and I do want to help. You say that you don't need to provide any more code. Myself and others say that we need more. I think that creating your binding in the enclosing view using [closures](https://developer.apple.com/documentation/swiftui/binding/init(get:set:)-6g3d5) is probably what you need. – Paulw11 Jan 10 '23 at 09:44

1 Answers1

1

This is really a most entertaining comment thread and it feels like reverse engineering code from a textual descriptions can become a new sport. Might be something for ChatGPT ;) And yes, I will delete this intro soon.

But here comes a suggestion for what I tried to understand. isFavourite can be toggled either by the new .contextMenu(forSelectionType) or the described FavoriteButton. I might be way off, just let me know :)

struct Item: Identifiable {
    let id = UUID()
    var name: String
    var isFavorite = false
}

struct ContentView : View {
    
    init() {
        var dummy = [Item]()
        for i in 1...10 {
            dummy.append(Item(name: "Item \(i)"))
        }
        self._data = State(initialValue: dummy)
    }
    
    @State private var data: [Item]
    @State private var selection: UUID?
    
    var body: some View {
        List(data, selection: $selection) { item in
            HStack {
                Image(systemName: "heart").opacity(item.isFavorite ? 1 : 0)
                Text(item.name)
                Spacer()
                FavoriteButton(items: $data, selection: item.id)
            }
            .swipeActions {
                FavoriteButton(items: $data, selection: item.id)
            }
        }
        .contextMenu(forSelectionType: UUID.self)  { indices in
            FavoriteButton(items: $data, selection: indices.first)
        }
    }
}


struct FavoriteButton: View {
    
    @Binding var items : [Item]
    let selection: Item.ID?
    
    var body: some View {
        Button("Toggle") {
            if let index = items.firstIndex(where: { $0.id == selection }) {
                items[index].isFavorite.toggle()
            }
        }
    }
}

Edit: now with FavoriteButton also used inside contextMenu.
Edit2: now with FavoriteButton also used in .swipeActions

ChrisR
  • 9,523
  • 1
  • 8
  • 26
  • You can keep this for clarification if someone needs it. This is exactly the problem. As you can see there he uses the Button in the contextMenu and not FavoriteButton. If you can use the FavoriteButton there you have resolved the problem! – Ivan Ičin Jan 09 '23 at 22:34
  • so you want to use FavoriteButton inside the contextMenu? – ChrisR Jan 09 '23 at 22:36
  • Whooh, I was a bit too fast. No, the point would be to use the FavoriteButton in .swipeActions as you don't have items to pass there. And that selection parameter is not needed. But still thanks for the code I have added it above. – Ivan Ičin Jan 09 '23 at 22:45
  • I updated the code, now includes FavoriteButton in .swipeActions ... – ChrisR Jan 09 '23 at 22:57
  • I’give you the upvote for although I don’t think it is the answer but it made me think of few more solutions. I did update the question which is now very clear I think. Though all those elements were in the original question. – Ivan Ičin Jan 09 '23 at 23:02