Почему `.onAppear` вызывается дважды, когда view находится внутри TabView

#swift #swiftui

#swift #swiftui

Вопрос:

Я пытаюсь загрузить удаленный ресурс, поэтому я использую .onAppear для этого. Однако, когда я использую if/else внутри представления, это приводит к его срабатыванию дважды.

 class FooVM: ObservableObject {
   @Published var remoteText: String? = nil

   func load() {
      DispatchQueue.main.asyncAfter(deadline: .now()   2) {
         self.remoteText = "(Date())"
      }
   }
}

struct Foo: View {

   @StateObject private var vm = FooVM()

   var body: some View {
      content
         .onAppear(perform: vm.load)
   }

   @ViewBuilder
   private var content: some View {
      if let text = vm.remoteText {
         Text(text)
      } else {
         Circle()
      }
   }
}

 
 struct ContentView: View {
    var body: some View {
        TabView {
            Foo()
                .tabItem {
                    Image(systemName: "circle.fill")
                    Text("Home")
                } 
        }
    }
}
 

РЕДАКТИРОВАТЬ: оказывается, он воспроизводится только тогда, когда содержимое находится внутри TabView . Я обновил приведенный выше код

Почему onAppear (и, следовательно load ,) запускается дважды? На самом деле, на игровой площадке он срабатывает повторно каждые 2 секунды, но в симуляторе срабатывает только дважды.

Как мне этого избежать? Имейте в виду, что это упрощенный пример.


Интересно, что если бы это был не выбор между Text и Circle , а просто другой текст внутри Text , тогда он сработал бы только один раз:

 Text(vm.remoteText ?? "loading...")
 

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

1. С Xcode 12.1 / iOS 14.1 load вызывается только один раз с предоставленным кодом. Какую среду вы использовали?

2. Xcode 12.2 / iOS 14.2

3. Я нахожусь на 12.2 / iOS 14.2 и вижу, что load вызывается только один раз. Вы пробовали на симуляторе / за пределами игровой площадки?

4. @nicksarno, да — пробую это на симуляторе на самом деле

5. Ну, я не могу воспроизвести это с текущим кодом. Единственное, что приходит на ум, это, возможно, представление будет «появляться» при переключении содержимого. Попробуйте внедрить ‘content’ в ZStack и вместо этого вызвать .onAppear в ZStack. Таким образом, ZStack сохраняется, пока содержимое снова появляется? Просто догадываюсь, ха-ха

Ответ №1:

Когда ваш контент переключается в операторе if, Foo () должен полностью появиться снова. Решение состоит в том, чтобы встроить содержимое Foo () в ZStack (или аналогичный), чтобы ему не нужно было повторно отображать все представление и снова вызывать .onAppear. Здесь ZStack остается отображаемым при переключении содержимого.

 var body: some View {
        ZStack {
            content
        }
        .onAppear(perform: vm.load)
   }
 

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

1. Действительно, использование VStack or ZStack (но не Group ) решает эту проблему. Спасибо! Я не думаю, однако, что причина, которую вы указали, т. Е. Foo Требуется повторный рендеринг, верна, поскольку Foo не требуется повторный рендеринг, когда он не находится внутри a TabView . Кроме того, почему VStack / ZStack устраняет необходимость повторного рендеринга, но нет _ConditionalContent<Text, Circle> (что content и есть). Если вы могли бы изменить объяснение, я приму это как ответ. Спасибо

2. Я думаю, повторный рендеринг был неправильным словом. Похоже, что это больше связано с размещением .onAppear, чем с TabView. Если у вас есть полное объяснение этому, не стесняйтесь добавлять свой собственный ответ выше моего.

3. Спасибо @nicksarno за ваше решение, оно помогло мне. Просто добавляю небольшое примечание, поскольку кто-то может столкнуться с моей проблемой, когда я добавлял встроенный мой контент в ZStack, у него было большое пространство сверху, поэтому я добавил (alignment: .top) в ZStack.