【问题标题】:SwifUI onAppear gets called twiceSwiftUI onAppear 被调用两次
【发布时间】:2020-11-14 17:57:21
【问题描述】:

Q1:为什么 onAppears 会被调用两次?

Q2:或者,我可以在哪里拨打网络电话?

我在代码中的几个不同位置放置了 onAppears,它们都被调用了两次。最终,我试图在显示下一个视图之前进行网络调用,所以如果你知道不使用 onAppear 的方法,我会全力以赴。

我还尝试在我的列表中放置和删除 ForEach,但它并没有改变任何东西。

Xcode 12 Beta 3 -> 目标 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")
        }) 
    }
}

【问题讨论】:

    标签: list networking swiftui navigationlink onappear


    【解决方案1】:

    我也有同样的问题。

    我做了以下事情:

    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 {
                appearCount += 1
                //This is where I loaded my coreData information into normal arrays
            }
            didAppear = true
        }
    }
    

    这通过确保只有 onLoad() 的 if 条件内部的内容才会运行一次来​​解决这个问题。

    更新:Apple 开发者论坛上的某个人提出了投诉,Apple 已意识到该问题。在 Apple 解决问题之前,我的解决方案是临时破解。

    【讨论】:

    • 是的,我刚刚验证了这在 15b1 中已修复。呃,我希望他们能向后移植这样的东西。
    • 当视图被重新实例化时这不起作用,这就是我的情况
    【解决方案2】:

    你可以创建一个布尔变量来检查是否首先出现

    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
        })
      }
    }
    

    【讨论】:

      【解决方案3】:

      您可以为此错误创建第一个出现函数

      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()
              }
          }
      }
      

      【讨论】:

      • 这使我的模拟器崩溃,queue.addOperation(delayOperation) 行出现此错误:*** -[NSOperationQueue addOperation:]: operation is already enqueued on a queue
      • @Jeehut 我更新了答案 :) 但我认为 Apple 正在解决这个问题,自上次 XCode 更新以来,我收到的重复 onAppear 调用减少了
      【解决方案4】:

      我一直在使用类似的东西

         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()
       }
      

      【讨论】:

        【解决方案5】:

        对于仍然遇到此问题并使用 NavigationView 的所有人。将此行添加到根 NavigationView() 应该可以解决问题。

        .navigationViewStyle(StackNavigationViewStyle())
        

        从我尝试过的所有方法来看,这是唯一有效的方法。

        【讨论】:

        • 这对我不起作用。
        【解决方案6】:

        我们不必在.onAppear(perform) 上这样做 这可以在 View 的 init 上完成

        【讨论】:

        • 我将此解决方案与 ListView 和 DetailView 一起使用。在详细视图的初始化中,我正在调用我的演示者以从核心数据中获取数据。到目前为止,我只看到了 init 调用了一次,仍然看到了 onAppear 调用了 2 次。我喜欢这种方法,因为我不必在每个视图中添加任何 didAppear 布尔标志。
        • 这个解决方案很糟糕,但似乎对我来说也最有效,因为我确实需要在每次我的视图再次出现时执行代码,而不仅仅是第一次。否则,每次加载数据时都会重复调用 onAppear。
        【解决方案7】:

        让我们假设您现在正在设计 SwiftUI,并且您的 PM 也是物理学家和哲学家。有一天他告诉你我们应该统一 UIView 和 UIViewController,就像量子力学和相对论一样。 OK,你和你的领导志同道合,投票支持“Simplicity is Tao”,并创建一个名为“View”的原子。现在你说:“视野就是一切,视野就是一切”。这听起来很棒,而且似乎可行。好吧,你提交代码并告诉 PM……。

        onAppearonDisAppear 存在于每个视图中,但您真正需要的是页面生命周期回调。如果你像viewDidAppear 一样使用onAppear,那么你会遇到两个问题:

        1. 受父视图影响,子视图会多次重建,导致onAppear被多次调用。
        2. SwiftUI 是封闭源代码,但您应该知道:view = f(view)。所以,onAppear 将运行以返回一个新视图,这就是为什么 onAppear 会被调用两次。

        我想告诉你onAppear是对的!你必须改变你的想法。不要在onAppearonDisAppear 中运行生命周期代码!您应该在“行为区域”中运行该代码。例如,在导航到新页面的按钮中。

        【讨论】:

        • 你是对的绅士!谢谢我在init()而不是onAppear中制定了我的逻辑。谢谢!
        【解决方案8】:

        如果其他人在我的船上,我现在是这样解决的:

        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")
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案9】:

          我有这个应用程序:

          @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() 调用之间的 diff time 解决了这个问题。根据我的观察, 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
              }
              
          }
          

          【讨论】:

            【解决方案10】:

            就我而言,我发现层次结构中的一些视图,.onAppear()(和.onDisappear())只被调用一次,正如预期的那样。我用它来发布我在需要对这些事件采取行动的视图中收听的通知。这是一个严重的黑客攻击,我已经验证该错误已在 iOS 15b1 中修复,但 Apple 确实需要向后移植该修复程序。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-08-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-05
              • 2021-05-26
              • 2021-02-24
              相关资源
              最近更新 更多