SwifUI onAppear вызывается дважды

Q1: Почему onAppears вызывается дважды?

Q2: Как я могу позвонить в сеть?

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

Я также попытался разместить и удалить ForEach в своих списках, и это ничего не меняет.

Xcode 12 Beta 3 -> Target iOs 14 CoreData включен, но еще не используется

struct ChannelListView: View {

@EnvironmentObject var channelStore: ChannelStore
@State private var searchText = ""
@ObservedObject private var networking = Networking()

var body: some View {
    NavigationView {
        VStack {
            SearchBar(text: $searchText)
                .padding(.top, 20)
             
            List() {

                ForEach(channelStore.allChannels) { channel in
                    
                    NavigationLink(destination: VideoListView(channel: channel)
                                    .onAppear(perform: {
                        print("PREVIOUS VIEW ON APPEAR")
                    })) {
                        ChannelRowView(channel: channel)
                    }
                }
                .listStyle(GroupedListStyle())
            }
            .navigationTitle("Channels")
            }
        }
    }
}

struct VideoListView: View {

@EnvironmentObject var videoStore: VideoStore
@EnvironmentObject var channelStore: ChannelStore
@ObservedObject private var networking = Networking()

var channel: Channel

var body: some View {
    
    List(videoStore.allVideos) { video in
        VideoRowView(video: video)
    }
        .onAppear(perform: {
            print("LIST ON APPEAR")
        })
        .navigationTitle("Videos")
        .navigationBarItems(trailing: Button(action: {
            networking.getTopVideos(channelID: channel.channelId) { (videos) in
                var videoIdArray = [String]()
                videoStore.allVideos = videos
                
                for video in videoStore.allVideos {
                    videoIdArray.append(video.videoID)
                }
                
                for (index, var video) in videoStore.allVideos.enumerated() {
                    networking.getViewCount(videoID: videoIdArray[index]) { (viewCount) in
                        video.viewCount = viewCount
                        videoStore.allVideos[index] = video
                        
                        networking.setVideoThumbnail(video: video) { (image) in
                            video.thumbnailImage = image
                            videoStore.allVideos[index] = video
                        }
                    }
                }
            }
        }) {
            Text("Button")
        })
        .onAppear(perform: {
            print("BOTTOM ON APPEAR")
        }) 
    }
}

10 ответов

Я использовал что-то вроде этого

         import SwiftUI

    struct OnFirstAppearModifier: ViewModifier {
      let perform:() -> Void
      @State private var firstTime: Bool = true
   
   func body(content: Content) -> some View {
       content
           .onAppear{
               if firstTime{
                   firstTime = false
                   self.perform()
               }
           }
     } 
  }


   extension View {
      func onFirstAppear( perform: @escaping () -> Void ) -> some View {
        return self.modifier(OnFirstAppearModifier(perform: perform))
      }
   }

и я использую его вместо .onAppear()

       .onFirstAppear{
   self.vm.fetchData()
 }

У меня была такая же проблема.

Я сделал следующее:

struct ContentView: View {

    @State var didAppear = false
    @State var appearCount = 0

    var body: some View { 
       Text("Appeared Count: \(appearrCount)"
           .onAppear(perform: onLoad)
    }

    func onLoad() {
        if didAppear = false {
            appearCount += 1
            //This is where I loaded my coreData information into normal arrays
        }
        didAppear = true
    }
}

Это решает проблему, гарантируя, что только то, что находится внутри условного if внутри onLoad(), будет выполняться один раз.

Обновление: кто-то на форумах разработчиков Apple подал заявку, и Apple знает об этой проблеме. Мое решение - временный взлом, пока Apple не решит проблему.

Предположим, вы сейчас разрабатываете SwiftUI, а ваш менеджер проектов также является физиком и философом. Однажды он скажет вам, что мы должны объединить UIView и UIViewController, как квантовую механику и теорию относительности. Хорошо, вы единомышленники со своим лидером, голосуете за «Простота — это Дао» и создаете атом под названием «Вид». Теперь вы говорите: «Вид — это все, вид — это все». Это звучит потрясающе и кажется осуществимым. Ну, вы фиксируете код и сообщаете PM….

и существует в каждом представлении, но вам действительно нужен обратный вызов жизненного цикла страницы. Если вы используете как viewDidAppear, то вы получаете две проблемы:

  1. Под влиянием родителя дочернее представление будет перестраиваться более одного раза, вызывая многократный вызов.
  2. SwiftUI имеет закрытый исходный код, но вы должны это знать: view = f(view). Итак, будет работать, чтобы вернуть новое представление, поэтому вызывается дважды.

Я хочу сказать вам, что это правильно! ВЫ ДОЛЖНЫ ИЗМЕНИТЬ СВОИ ИДЕИ. Не запускайте код жизненного цикла в onAppearа также onDisAppear! Вы должны запустить этот код в «Области поведения». Например, в кнопке перехода на новую страницу.

Вы можете создать переменную типа bool, чтобы проверить, появляется ли сначала

struct VideoListView: View {
  @State var firstAppear: Bool = true

  var body: some View {
    List {
      Text("")
    }
    .onAppear(perform: {
      if !self.firstAppear { return }
      print("BOTTOM ON APPEAR")
      self.firstAppear = false
    })
  }
}

Для всех, у кого все еще есть эта проблема и которые используют NavigationView. Добавьте эту строку в корневой NavigationView(), и это должно решить проблему.

      .navigationViewStyle(StackNavigationViewStyle())

Из всего, что я пробовал, это единственное, что сработало.

Вы можете создать функцию первого появления для этой ошибки

      extension View {

    /// Fix the SwiftUI bug for onAppear twice in subviews
    /// - Parameters:
    ///   - perform: perform the action when appear
    func onFirstAppear(perform: @escaping () -> Void) -> some View {
        let kAppearAction = "appear_action"
        let queue = OperationQueue.main
        let delayOperation = BlockOperation {
            Thread.sleep(forTimeInterval: 0.001)
        }
        let appearOperation = BlockOperation {
            perform()
        }
        appearOperation.name = kAppearAction
        appearOperation.addDependency(delayOperation)
        return onAppear {
            if !delayOperation.isFinished, !delayOperation.isExecuting {
                queue.addOperation(delayOperation)
            }
            if !appearOperation.isFinished, !appearOperation.isExecuting {
                queue.addOperation(appearOperation)
            }
        }
        .onDisappear {
            queue.operations
                .first { $0.name == kAppearAction }?
                .cancel()
        }
    }
}

Нам не нужно это делать .onAppear(perform)Это можно сделать при инициализации View

У меня есть это приложение:

      @main
struct StoriesApp: App {
    
    var body: some Scene {
        WindowGroup {
            TabView {
                NavigationView {
                    StoriesView()
                }
            }
        }
    }
    
}

А вот и мой StoriesView:

      // ISSUE

struct StoriesView: View {
    
    @State var items: [Int] = []
    
    var body: some View {
        List {
            ForEach(items, id: \.self) { id in
                StoryCellView(id: id)
            }
        }
        .onAppear(perform: onAppear)
    }
    
    private func onAppear() {
        ///////////////////////////////////
        // Gets called 2 times on app start <--------
        ///////////////////////////////////
    }
    
}

Я решил проблему, измерив время разницы между вызовами onAppear(). По моим наблюдениям, двойные вызовы onAppear () происходят между 0,02 и 0,45 секундами:

      // SOLUTION

struct StoriesView: View {
    
    @State var items: [Int] = []
    
    @State private var didAppearTimeInterval: TimeInterval = 0
    
    var body: some View {
        List {
            ForEach(items, id: \.self) { id in
                StoryCellView(id: id)
            }
        }
        .onAppear(perform: onAppear)
    }
    
    private func onAppear() {
        if Date().timeIntervalSince1970 - didAppearTimeInterval > 0.5 {
            ///////////////////////////////////////
            // Gets called only once in 0.5 seconds <-----------
            ///////////////////////////////////////
        }
        didAppearTimeInterval = Date().timeIntervalSince1970
    }
    
}

В моем случае я обнаружил, что несколько просмотров вверх по иерархии, (а также ), как и ожидалось, вызвали только один раз. Я использовал это, чтобы публиковать уведомления, которые я слушаю в представлениях, которые должны действовать в отношении этих событий. Это грубый взлом, и я убедился, что ошибка исправлена ​​в iOS 15b1, но Apple действительно нужно перенести исправление.

Если в моей лодке находится кто-то еще, вот как я решил это сейчас:

struct ChannelListView: View {

@State private var searchText = ""
@State private var isNavLinkActive: Bool = false
@EnvironmentObject var channelStore: ChannelStore
@ObservedObject private var networking = Networking()

var body: some View {
    NavigationView {
        VStack {
            SearchBar(text: $searchText)
                .padding(.top, 20)
            List(channelStore.allChannels) { channel in
                ZStack {
                    NavigationLink(destination: VideoListView(channel: channel)) {
                        ChannelRowView(channel: channel)
                    }
                    HStack {
                        Spacer()
                        Button {
                            isNavLinkActive = true
                            
                            // Place action/network call here
                            
                        } label: {
                            Image(systemName: "arrow.right")
                        }
                        .foregroundColor(.gray)
                    }
                }
                .listStyle(GroupedListStyle())
            }
            .navigationTitle("Channels")
            }
        }
    }
}
Другие вопросы по тегам