SwifUI onAppear 被调用两次

Avs*_*est 22 networking list swiftui navigationlink 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")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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")
        }) 
    }
}
Run Code Online (Sandbox Code Playgroud)

Tap*_*asa 15

我一直在使用这样的东西

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))
    }
}
Run Code Online (Sandbox Code Playgroud)

我用它代替 .onAppear()

 .onFirstAppear{
   self.vm.fetchData()
 }
Run Code Online (Sandbox Code Playgroud)


小智 14

让我们假设您现在正在设计 SwiftUI,并且您的产品经理也是一位物理学家和哲学家。有一天他告诉你我们应该统一 UIView 和 UIViewController,就像量子力学和相对论一样。OK,你和你的leader志同道合,投票给“简即道”,创建一个名为“View”的原子。现在你说:“景色就是一切,景色就是一切”。这听起来很棒而且似乎可行。好吧,你提交代码并告诉 PM\xe2\x80\xa6。

\n

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

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

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

\n


vdo*_*tup 8

您可以创建一个 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
    })
  }
}
Run Code Online (Sandbox Code Playgroud)


Jus*_*ral 7

我有同样的问题。

我所做的是以下内容:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

更新:Apple 开发者论坛上的某个人提交了一张票,Apple 已经意识到这个问题。我的解决方案是临时破解,直到 Apple 解决问题。

  • 当重新实例化视图时,这不起作用,这就是我的情况 (6认同)

cse*_*_17 6

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

.navigationViewStyle(StackNavigationViewStyle())
Run Code Online (Sandbox Code Playgroud)

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

  • 这对我不起作用。 (2认同)

Cal*_*ang 5

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

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)