SwifUI onAppear被调用两次

时间:2020-07-24 20:14:12

标签: list networking swiftui navigationlink onappear

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

第二季度:或者,我可以在哪里拨打网络电话?

我在代码中的几个不同位置放置了onAppears,它们都被调用了两次。最终,我会尝试在显示下一个视图之前进行网络呼叫,因此,如果您知道无需使用onAppear即可进行此操作的方法,那么我无所不能。

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

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

8 个答案:

答案 0 :(得分:3)

我有同样的问题。

我所做的是:

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

这通过确保onLoad()的if条件内部的内容仅运行一次来​​解决。

更新:Apple Developer论坛上的某人已提交了票证,Apple知道了此问题。我的解决方案是暂时的措施,直到Apple解决问题为止。

答案 1 :(得分:1)

我一直在使用这样的东西

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

答案 2 :(得分:0)

您可以创建一个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
    })
  }
}

答案 3 :(得分:0)

万一有人在我的船上,这是我现在解决的方法:

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

您可以为此错误创建首次出现的功能

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 {
            queue.addOperation(delayOperation)
            queue.addOperation(appearOperation)
        }
        .onDisappear {
            queue.operations
                .first { $0.name == kAppearAction }?
                .cancel()
        }
    }
}

答案 6 :(得分:0)

我有这个应用程序:

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

答案 7 :(得分:0)

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