SwiftUI измените значение в списке, нажав

#swiftui

#swiftui

Вопрос:

Сегодня я с чем-то борюсь и не могу найти для этого хорошего решения. У меня есть простой класс (User) с некоторыми атрибутами и другой класс (UsersContainer) в качестве ObservableObject с массивом первого класса:

 struct User: Identifiable {
    let id = UUID()
    var name: String
    var isActive: Bool
}

class UsersContainer : ObservableObject{
    @Published var users = [User]()
    
    init() {
        users = [User(name: "John", isActive: true), User(name: "Paul", isActive: true), User(name: "Jack", isActive: false)]
    }
}
  

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

 struct ContentView: View {
    
    @ObservedObject var usersContainer = UsersContainer()
    
    var body: some View {
        VStack{
            List(usersContainer.users, id: .id){user in
                Text(user.name)
                    .strikethrough(!user.isActive)
                    .foregroundColor(user.isActive ? Color.black : Color.gray)
                    .onTapGesture {
                        print("Tap")
                    }
            }
        }
    }
}
  

Результат в эмуляторе — это то, что я ожидал:

скриншот из эмулятора

Моя проблема в TapGesture в contentView. Я хочу изменить строку, когда пользователь нажимает на строку с помощью этого кода:

 .onTapGesture {
      user.isActive.toggle()
}
  

Но я получаю сообщение об ошибке:

Невозможно использовать изменяемый элемент для неизменяемого значения: ‘user’ — это константа ‘let’

Как я могу изменить значение пользователя на переменную?

Я надеюсь, что смог хорошо объяснить свою проблему.

Спасибо за помощь!

Ответ №1:

Вот возможный подход

 List(Array(usersContainer.users.enumerated()), id: .1.id) { index, user in
    Text(user.name)
        .strikethrough(!user.isActive)
        .foregroundColor(user.isActive ? Color.black : Color.gray)
        .onTapGesture {
           self.usersContainer.users[index].isActive.toggle() 
        }
}
  

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

1. Это работает! В этом случае вы меняете источник списка, а не объект в каждой строке. Это нормальный подход / лучшая практика в данном случае? Спасибо

2. Это соответствует концепции единого источника истины.

Ответ №2:

SwiftUI 2022

Одна из наиболее важных концепций, которую вы должны иметь в виду, — это источник истины и способы обработки этого из разных представлений в вашем приложении.

В вашем примере источник истины находится в вашем представлении содержимого. И вы пытаетесь манипулировать или изменять этот источник истины из другого представления в иерархии представлений.

В вашем примере вы изменяете его из onTapGesture в TextView.

 Text(user.name)
       .strikethrough(!user.isActive)
       .foregroundColor(user.isActive ? Color.black : Color.gray)
       .onTapGesture {
           print("Tap")
           user.isActive.toggle()
       }
  

Используя оболочку свойства @ObservedObject в вашем contentView для UsersContainer(), дает вам возможность «привязать» этот источник истины к другим представлениям. Это означает, что это позволит этим представлениям изменять данные, которые принадлежат где-то выше в иерархии представлений, и в то же время запускать повторный рендеринг представлений.

В списках теперь есть новый API, который значительно упрощает ожидание.

Ваш код будет выглядеть примерно так:

 
struct ContentView: View {
    
    @ObservedObject var usersContainer = UsersContainer()
    
    var body: some View {
        VStack{
            List($usersContainer.users){ $user in
                Text(user.name)
                    .strikethrough(!user.isActive)
                    .foregroundColor(user.isActive ? Color.black : Color.gray)
                    .onTapGesture {
                        user.isActive.toggle()
                        print("Tap")
                    }
            }
        }
    }
}

  

Префикс $ показывает, что вы передаете данные в представление, которое позволяет манипулировать его источником достоверности.

Несколько ключевых моментов, которые нужно извлечь из этого нового кода:

  1. Поскольку ваш «Пользователь» соответствует идентификатору, вы можете использовать список без параметра «id».
  2. Когда отображается представление, которому не принадлежат данные, используйте оболочку @Binding, чтобы иметь возможность манипулировать этими данными в иерархии.
  3. Всегда сохраняйте ясный источник истины

Счастливого кодирования!