I have list items in SwiftUI, and I want to move item on top when I set the favorite item with star icon, and I want to repeat it for second item again, is it possible? I know there is example for moving the row, but I do not understand how I will do like that.
Model:
struct Restaurant: Identifiable {
var id = UUID()
var name: String
var image: String
var isFavorite: Bool = false
}
BasicImageRow:
struct BasicImageRow: View {
var restaurant: Restaurant
var body: some View {
HStack {
Image(restaurant.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(restaurant.name)
if restaurant.isFavorite {
Spacer()
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
}
}
View:
import SwiftUI
struct ContentView: View {
@State private var selectedRestaurant: Restaurant?
@State private var restaurants = [ Restaurant(name: "Cafe Deadend", image: "cafedeadend"),
Restaurant(name: "Homei", image: "homei"),
Restaurant(name: "Teakha", image: "teakha"),
Restaurant(name: "Cafe Loisl", image: "cafeloisl"),
Restaurant(name: "Petite Oyster", image: "petiteoyster"),
Restaurant(name: "For Kee Restaurant", image: "forkeerestaurant"),
]
var body: some View {
List {
ForEach(restaurants) { restaurant in
BasicImageRow(restaurant: restaurant)
.contextMenu {
Button(action: {
// mark the selected restaurant as check-in
self.checkIn(item: restaurant)
}) {
HStack {
Text("Check-in")
Image(systemName: "checkmark.seal.fill")
}
}
Button(action: {
// delete the selected restaurant
self.delete(item: restaurant)
}) {
HStack {
Text("Delete")
Image(systemName: "trash")
}
}
Button(action: {
// mark the selected restaurant as favorite
self.setFavorite(item: restaurant)
}) {
HStack {
Text("Favorite")
Image(systemName: "star")
}
}
}
.onTapGesture {
self.selectedRestaurant = restaurant
}
.actionSheet(item: self.$selectedRestaurant) { restaurant in
ActionSheet(title: Text("What do you want to do"), message: nil, buttons: [
.default(Text("Mark as Favorite"), action: {
self.setFavorite(item: restaurant)
}),
.destructive(Text("Delete"), action: {
self.delete(item: restaurant)
}),
.cancel()
])
}
}
}
}
private func setFavorite(item restaurant: Restaurant) {
if let index = self.restaurants.firstIndex(where: { $0.id == restaurant.id }) {
self.restaurants[index].isFavorite.toggle()
}
}
}
CodePudding user response:
One way would be to have a FavoriteModel
that would keep track of when the item was marked.
struct FavoriteModel{
var isFavorite: Bool = false
var timeStamp: Date = Date()
mutating func toogleFavorite(){
isFavorite.toggle()
timeStamp = Date()
}
}
Then sort the items by the timestamp
ForEach(restaurants.sorted(by: {
$0.isFavorite.timeStamp > $1.isFavorite.timeStamp
})) { restaurant in
It some use cases it might even be beneficial to keep track of the people that have selected the item as favorite
struct FavoriteModel{
var isFavorite: Bool = false
var timeStamp: Date = Date()
var userIds: Set<UUID> = []
mutating func toogleFavorite(userId: UUID){
isFavorite.toggle()
timeStamp = Date()
if isFavorite{
userIds.insert(userId)
}else{
userIds.remove(userId)
}
}
}
Below is a full set of working code.
struct Restaurant: Identifiable {
var id = UUID()
var name: String
var image: String
var isFavorite: FavoriteModel = .init()
}
struct BasicImageRow: View {
var restaurant: Restaurant
var body: some View {
HStack {
Image(restaurant.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(restaurant.name)
if restaurant.isFavorite.isFavorite {
Spacer()
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
}
}
struct RestaurantView: View {
@State private var selectedRestaurant: Restaurant?
@State private var restaurants = [ Restaurant(name: "Cafe Deadend", image: "cafedeadend"),
Restaurant(name: "Homei", image: "homei"),
Restaurant(name: "Teakha", image: "teakha"),
Restaurant(name: "Cafe Loisl", image: "cafeloisl"),
Restaurant(name: "Petite Oyster", image: "petiteoyster"),
Restaurant(name: "For Kee Restaurant", image: "forkeerestaurant"),
]
var body: some View {
List {
ForEach(restaurants.sorted(by: {
$0.isFavorite.timeStamp > $1.isFavorite.timeStamp
})) { restaurant in
BasicImageRow(restaurant: restaurant)
.contextMenu {
Button(action: {
// mark the selected restaurant as check-in
//self.checkIn(item: restaurant)
}) {
HStack {
Text("Check-in")
Image(systemName: "checkmark.seal.fill")
}
}
Button(action: {
// delete the selected restaurant
//self.delete(item: restaurant)
}) {
HStack {
Text("Delete")
Image(systemName: "trash")
}
}
Button(action: {
// mark the selected restaurant as favorite
self.setFavorite(item: restaurant)
}) {
HStack {
Text("Favorite")
Image(systemName: "star")
}
}
}
.onTapGesture {
self.selectedRestaurant = restaurant
}
.actionSheet(item: self.$selectedRestaurant) { restaurant in
ActionSheet(title: Text("What do you want to do"), message: nil, buttons: [
.default(Text("Mark as Favorite"), action: {
self.setFavorite(item: restaurant)
}),
.destructive(Text("Delete"), action: {
//self.delete(item: restaurant)
}),
.cancel()
])
}
}
}
}
private func setFavorite(item restaurant: Restaurant) {
if let index = self.restaurants.firstIndex(where: { $0.id == restaurant.id }) {
self.restaurants[index].isFavorite.toogleFavorite()
}
}
}
struct RestaurantView_Previews: PreviewProvider {
static var previews: some View {
RestaurantView()
}
}
CodePudding user response:
You just need to update restaurants inside withAnimation
block, SwiftUI will know how to animate them with their id
ForEach(restaurants) { restaurant in
Button(action: {
withAnimation {
var favoritedRestaurant = restaurant
favoritedRestaurant.isFavorite = true
restaurants = [favoritedRestaurant] restaurants.filter { $0.id != restaurant.id }
}
}) {
BasicImageRow(restaurant: restaurant)
}
}