Дочерние подвиды не сбрасываются при создании нового родителя в SwiftUI - PullRequest
2 голосов
/ 03 августа 2020

Вчера я написал об этом пост и прошу прощения за то, что он недостаточно ясен или описан. Сегодня я добился большего прогресса в решении проблемы, но до сих пор не нашел решения.

В моей программе у меня есть главное представление с именем GameView (), представление с именем KeyboardView () и представление с именем ButtonView ().

ButtonView () - это простая кнопка, которая отображает букву и при нажатии сообщает KeyboardView, что она принадлежит той букве, которую она представляет. Когда он нажимается, он также отключается, поэтому его нельзя нажать снова. Вот код.

struct ButtonView: View {
    
    let impactFeedbackgenerator = UIImpactFeedbackGenerator(style: .medium)
    var letter:String
    var function:() -> Void
    @State var pressed = false
    
    var body: some View {
        ZStack{
            
            Button(action: {
                if !self.pressed {
                    self.pressed = true
                    
                    self.impactFeedbackgenerator.prepare()
                    self.impactFeedbackgenerator.impactOccurred()
                    
                    self.function()
                }
            }, label: {
                if pressed{
                    Text(letter)
                    .font(Font.custom("ComicNeue-Bold", size: 30))
                    .foregroundColor(.white)
                    .opacity(0.23)
                } else if !pressed{
                    Text(letter)
                    .font(Font.custom("ComicNeue-Bold", size: 30))
                    .foregroundColor(.white)
                }
            })
        }.padding(5)
    }
}

Вид с клавиатуры - это набор элементов ButtonView (), по одному для каждой кнопки на клавиатуре. Он сообщает GameView, какая кнопка была нажата, если была нажата кнопка.

struct KeyboardView: View {
    
    @Environment(\.parentFunction) var parentFunction
    
    var topRow = ["Q","W","E","R","T","Y","U","I","O","P"]
    var midRow = ["A","S","D","F","G","H","J","K","L"]
    var botRow = ["Z","X","C","V","B","N","M"]
    
    
    var body: some View {
         VStack{
    
            HStack(){
                ForEach(0..<topRow.count, id: \.self){i in
                    ButtonView(letter: self.topRow[i], function: {self.makeGuess(self.topRow[i])})
                }
            }
            
            HStack(){
                ForEach(0..<midRow.count, id: \.self){i in
                    ButtonView(letter: self.midRow[i], function: {self.makeGuess(self.midRow[i])})
                }
            }
            
            HStack(){
                ForEach(0..<botRow.count, id: \.self){i in
                    ButtonView(letter: self.botRow[i], function: {self.makeGuess(self.botRow[i])})
                }
            }
        }
    }
    
    func makeGuess(_ letter:String){
        print("Keyboard: Guessed \(letter)")
        self.parentFunction?(letter)
    }
}

Наконец, GameView () - это место, где находится клавиатура. На нем отображается клавиатура вместе с остальной частью предполагаемой игры. остаются выключенными, но, поскольку он заменяется новым keyboardView (), не должны ли они go снова включиться?

1 Ответ

1 голос
/ 03 августа 2020

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

Всякий раз, когда происходит какое-либо изменение состояния, SwiftUI пересчитывает тело и реконструирует дерево представлений и сопоставляет состояния дочерних представлений с новым деревом.

Когда он обнаруживает, что что-то изменилось, он понимает, что новый дочерний элемент view действительно new , поэтому он сбрасывает свое состояние, запускает .onAppear и так далее. Но когда нет изменений, которые он может обнаружить, он просто сохраняет то же состояние для всех дочерних представлений.

Это то, что вы наблюдаете.

В частности, в вашей ситуации ничего структурно не имеет изменено - то есть он все еще:

GameView
 --> KeyboardView
      --> ButtonView
          ButtonView
          ButtonView
          ...

поэтому он сохраняет состояние ButtonView s как есть.

Вы можете сигнализировать SwiftUI, что в представлении есть на самом деле изменено и что его следует обновить с помощью модификатора .id (документация невелика, но вы можете найти дополнительную информацию в блогах), где вам необходимо указать любую переменную Hashable которая отличается от текущей, чтобы ее сбросить.

Я буду использовать новую переменную Bool в качестве примера:

struct GameView {
  @State var id: Bool = false // initial value doesn't matter

  var body: some View {
      VStack() {
         KeyboardView()
            .id(id)  // use the id here
         Button("new game") {
            self.id.toggle() // changes the id
         }
      }
  }
}

Каждый раз, когда id изменяется, SwiftUI сбрасывает состояние, поэтому все дочерние представления, например, ButtonView s, сбрасываются.

...