5

I am attempting to create a favorites functionality using a List with two ForEach loops. When I click on the button, the item moves to the correct section, but the button image and functionality are not updated. Is there something I am missing or doing incorrectly?

If I navigate to another view and back, the list is rendered correctly initially, but still exhibits the same behavior if I click on the button.

If I use two separate lists with exactly the same setup, everything works fine, but that looks strange in the UI because each list takes up the same amount of room even if there are no items in the list.

Alternatively, is there a way to force the list to redraw as part of the button tap?

Thanks!

import SwiftUI

struct BusinessListView: View {

    @EnvironmentObject var state: ApplicationState

    var body: some View {
        VStack{

            List {
                Section(header: Text("Favorites")) {

                    if(state.favorites.count == 0){

                        Text("No Favorites")
                    }
                    else {
                        ForEach(state.favorites, id: \.self) { favorite in
                            HStack{
                                Button(
                                    action: {},
                                    label: { Image(systemName: "heart.fill").foregroundColor(.red) }
                                )
                                    .onTapGesture { self.toggleFavorite(business: favorite)}
                                Text("\(favorite.name)")
                            }
                        }
                    }
                }

                Section(header: Text("Other Businesses")) {
                    ForEach(state.others, id: \.self) { business in
                        HStack{
                            Button(
                                action: {},
                                label: { Image(systemName: "heart") }
                            )
                                .onTapGesture { self.toggleFavorite(business: business)}
                            Text("\(business.name)")
                        }
                    }
                }
            }
        }
    }

    func toggleFavorite(business: Business){

        if(state.favorites.contains(business)){

            self.state.favorites = self.state.favorites.filter {$0 != business}

            self.state.others.append(business)
        }
        else{

            self.state.others = self.state.others.filter {$0 != business}

            self.state.favorites.append(business)
        }

        sortBusinesses()
    }


    func sortBusinesses(){

        self.state.favorites.sort {
            $0.name < $1.name
        }

        self.state.others.sort {
            $0.name < $1.name
        }
    }
}
5
  • would be great if you give us a reproducable compilaable example....stackoverflow.com/help/minimal-reproducible-example Commented May 27, 2020 at 17:58
  • @Chris - Here is the full code if you would like to take a look. It doesn't do a whole lot other than that at the moment. github.com/benbaran/tippler . I will try to create an example that does not need to do network calls, etc. Commented May 27, 2020 at 18:05
  • @Chris - Here is a minimal reproducible example: github.com/benbaran/swift-ui-favorites-list . Commented May 27, 2020 at 18:30
  • look to answer -> i corrected already Commented May 27, 2020 at 18:33
  • @Chris - Thanks for your help! But, the above answer doesn't actually work. The action and icon are not changed when the item moves. It just looks that way because the toggleFavorite() method will handle both scenarios. Commented May 27, 2020 at 19:10

2 Answers 2

2

UPDATED answer, works, but...

1) the trick was to change the id whenever you change the "cell" from favorite to non-favorite -> i assume Apple uses UITableView - logic with dequeueResubleCell and if the id is the same it won't be updated, instead just reused/copied and therefore the heart did not change

2) i change now the id randomly when favorites change - you have to think of a better/cleaner solution there.

struct BusinessListView: View {

    @EnvironmentObject var state: ApplicationState

    var body: some View {
        VStack{

            List {
                Section(header: Text("Favorites")) {

                    if(state.favorites.count == 0){

                        Text("No Favorites")
                    }
                    else {
                        ForEach(state.favorites, id: \.self) { favorite in
                            HStack{
                                Button(
                                    action: {
                                        self.toggleFavorite(business: favorite)
                                },
                                    label: {
                                        HStack {
                                            Image(systemName: "heart.fill").foregroundColor(.red)
                                            Text("\(favorite.name)")
                                        }
                                }
                                ).id(favorite.id)
                            }
                        }
                    }
                }

                Section(header: Text("Other Businesses")) {
                    ForEach(state.others, id: \.self) { business in
                        HStack{
                            Button(
                                action: {
                                    self.toggleFavorite(business: business)
                            },
                                label: {
                                    HStack {
                                        Image(systemName: "heart")
                                        Text("\(business.name)")
                                    }
                            }
                            )
                            .id(business.id)
                        }
                    }
                }
            }
        }
    }

    func toggleFavorite(business: Business){

        if(state.favorites.contains(business)){

            self.state.favorites = self.state.favorites.filter {$0 != business}

            self.state.others.append(business)

            if let index = self.state.others.index(of: business) {
                self.state.others[index].id = Int.random(in: 10000...50000)
            }
        }
        else{

            self.state.others = self.state.others.filter {$0 != business}

            self.state.favorites.append(business)

            if let index = self.state.favorites.index(of: business) {
                self.state.favorites[index].id = Int.random(in: 10000...50000)
            }
        }

        sortBusinesses()
    }


    func sortBusinesses(){

        self.state.favorites.sort {
            $0.name < $1.name
        }

        self.state.others.sort {
            $0.name < $1.name
        }
    }
}

Result

Sign up to request clarification or add additional context in comments.

5 Comments

Hmmm. That doesn't seem to work for me either. Can you take a look at the minimal example? What I am seeing in that one is that the action and color is maintained even after the list item has moved. Maybe I just have a stupid mistake somewhere?
What color do you mean?
The heart should change to a filled red heart.
Basically the behavior I am seeing is that the item moves up to the favorites section, but the icon and action are not changed. If I navigate to another view and back, it is updated correctly.
Thank you!!! I feel like SwiftUI should handle this situation. I will post to the developer forums to see if they are interested in fixing it : )
0

have you verified .onTapGesture is being called? alternatively you could instead put self.toggleFavorite(business: favorite) inside action of the button.

                            Button(
                                action: { self.toggleFavorite(business: business) },
                                label: { Image(systemName: "heart") }
                            )

1 Comment

Yes, if I add a print statement, I can see that it is called. But it is always the onTapGesture for the add favorite button.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.