Добавление или удаление элемента из коллекции (set, array или dictionairy) с помощью переключателя SwiftUI

#swift #swiftui #toggle

#swift #swiftui #переключение

Вопрос:

Я создаю функцию, аналогичную функции «избранное» в примере приложения Apple Fruta SwiftUI. Пользователи этого приложения могут просматривать список смузи и отмечать любой смузи как «любимый». Список избранного сохраняется и отображается на отдельном экране.

Приведенный ниже пример представления выполняет две важные функции:

  1. Он проверяет, есть ли смузи уже в списке избранных isFavorite , чтобы это могло быть отражено изображением кнопки.
  2. Он добавляет / удаляет этот коктейль в / из списка избранного с toggleFavorite помощью .

Модель:

 struct Smoothie: Identifiable, Codable {
    var id: String
    var title: String
    var description: String
    var hasFreeRecipe = false
}

class FrutaModel: ObservableObject {
    @Published private(set) var favoriteSmoothieIDs = Set<Smoothie.ID>()

    func toggleFavorite(smoothie: Smoothie) {
        if favoriteSmoothieIDs.contains(smoothie.id) {
            favoriteSmoothieIDs.remove(smoothie.id)
        } else {
            favoriteSmoothieIDs.insert(smoothie.id)
        }
    }
    
    func isFavorite(smoothie: Smoothie) -> Bool {
        favoriteSmoothieIDs.contains(smoothie.id)
    }
}
 

Вид кнопки, который используется для (отмены) добавления смузи в избранное:

 struct SmoothieFavoriteButton: View {
    @EnvironmentObject private var model: FrutaModel
    
    var smoothie: Smoothie?
    
    var isFavorite: Bool {
        guard let smoothie = smoothie else { return false }
        return model.favoriteSmoothieIDs.contains(smoothie.id)
    }
    
    var body: some View {
        Button(action: toggleFavorite) {
            Label("Favorite", systemImage: isFavorite ? "heart.fill" : "heart")
        }
    }
    
    func toggleFavorite() {
        guard let smoothie = smoothie else { return }
        model.toggleFavorite(smoothie: smoothie)
    }
}
 

Моя проблема: как я могу добиться аналогичного результата при использовании переключателя вместо кнопки? Для переключения требуется привязка в качестве свойства, но поскольку isFavorite значение для конкретного смузи берется из Set ( favoriteSmoothieIDs ), который содержит полный список избранного, я не могу просто предоставить привязку для переключателя.

Комментарии:

1. Я полагаю, что кто-то уже ответил бы на это за вас, если бы этот код содержал список, на который вы ссылаетесь. (model.favoriteSmoothieIDs пуст). Пожалуйста, предоставьте достаточно кода, чтобы продемонстрировать вашу проблему. Это включает в себя добавление определения для Smoothie и представления, в котором отображается список.

2. Я только что добавил структуру, которая определяет Smoothie , если вы это имеете в виду. Я предполагаю, что не имеет значения, какие фактические значения хранятся в model.favoriteSmoothieIDs. Список избранных идентификаторов смузи может быть пустым, что также является состоянием по умолчанию, в котором пользователь еще не пометил ни один смузи как любимый.

3. ДА. Я понимаю. Я взял определение smoothie из оригинального руководства и запустил ваш код, прежде чем сделать свой первый комментарий. Запуск вашего кода как есть на самом деле ничего не делает. Кроме того: вы добавили MeasuredIngredient, который не определен, поэтому опять же кому-то трудно вам помочь, поскольку вы не предоставили значимого рабочего примера.

Ответ №1:

Это необходимый код:

 @State var isToggleFavorite = false

Toggle("", isOn: $isToggleFavorite)
            .onChange(of: isToggleFavorite) { value in
                guard let smoothie = smoothie else { return }
                model.toggleFavorite(smoothie: smoothie)
            }
 

Здесь я добавил код, который отвечает на ваш запрос, и оставил оригинал нетронутым.

 struct SmoothieFavoriteButton: View {
    @EnvironmentObject private var model: FrutaModel
    
    var smoothie: Smoothie?

    @State var isToggleFavorite = false // <== Newly Added

    var isFavorite: Bool {
        guard let smoothie = smoothie else { return false }
        return model.favoriteSmoothieIDs.contains(smoothie.id)
    }
    var body: some View {
        Toggle("", isOn: $isToggleFavorite)
            .onChange(of: isToggleFavorite) { value in
                guard let smoothie = smoothie else { return }
                model.toggleFavorite(smoothie: smoothie)
            } // <== Newly Added

        Button(action: toggleFavorite) {
            Label("Favorite", systemImage: isFavorite ? "heart.fill" : "heart")
        }
        .foregroundColor(isFavorite ? .accentColor : nil)
        .accessibility(label: Text("(isFavorite ? "Remove from" : "Add to") Favorites"))
        } // <== Newly Added
    } 
    
    func toggleFavorite() {
        guard let smoothie = smoothie else { return }
        model.toggleFavorite(smoothie: smoothie)
    }
}
 

[C]

Вы можете заменить кнопку текстовым объектом:

  Text(isToggleFavorite ? Image(systemName: "heart.fill"): Image(systemName: "heart"))
 

Но тогда вам нужно будет сохранить значение isFavorite.

Если наличие сердечка в левой части переключателя приемлемо, вы можете полностью удалить кнопку и поместить изображение сердечка в метку переключателя:

 Toggle(isOn: $isToggleFavorite) {
                isToggleFavorite ?
                    Image(systemName: "heart.fill") : Image(systemName: "heart")
            }
            .onChange(of: isToggleFavorite) { value in
                guard let smoothie = smoothie else { return }
                model.toggleFavorite(smoothie: smoothie)
            }
 

Вам доступно несколько опций.

Комментарии:

1. Спасибо за обширную помощь! Это решает 2-ю часть проблемы, но не 1-ю часть: проверьте, есть ли смузи уже в списке избранных при первой загрузке страницы, чтобы показать правильное состояние переключателя. Вы не можете решить эту проблему, установив правильное isToggleFavorite состояние в модификаторе onAppear, потому что это немедленно вызовет состояние onChange переключателя. Теперь я использую onTapGesture вместо onChange переключателя, но это не будет хорошо работать для переключений с меткой.

2. Я уже потратил на это пару часов. Если у меня будет возможность вернуться к нему, я это сделаю. Мне нужно сосредоточиться на других вещах. Подумайте о том, чтобы проголосовать за него, если мой ответ был полезен до сих пор, чтобы дать другим шанс найти его. Кто-то другой может дать вам ответ, который я не предоставил. Это было бы идеально. 🍀