在 SwiftUI 中使用 onAppear 时出现无限循环

Posted

技术标签:

【中文标题】在 SwiftUI 中使用 onAppear 时出现无限循环【英文标题】:Infinite loop when using onAppear in SwiftUI 【发布时间】:2021-10-04 20:13:24 【问题描述】:

我在使用 onAppear 时遇到了一个奇怪的无限循环,我无法确定问题的根源。只有当视图是导航视图的详细视图时才会发生这种情况,但当它是根视图时它可以正常工作。 另一个有趣的事情是,如果我将详细视图包装在 NavigationView 中(所以,现在我们在导航视图中有一个导航视图),那么问题就不会再出现了。这是 SwiftUI 中的错误吗?从概念上讲,我的设计可以吗?我的意思是,使用类似 viewDidLoad 的 onAppear 来触发初始序列。 感谢您的建议。

这里是源代码。 ContentView.swift:

import SwiftUI

struct ContentView: View 

    @StateObject var viewModel = ContentViewModel()

    var body: some View 
        NavigationView 
            VStack 
                Group 
                    switch viewModel.state 
                    case .loading:
                        Text("Loading...")
                    case .loaded:
                        HStack 
                            Text("Loaded")
                            Button("Retry") 
                                viewModel.fetchData()
                            
                        
                    
                
                .padding(.bottom, 20)
                NavigationLink("Go to detail screen", destination: DetailView())
            
        
        .onAppear() 
            viewModel.fetchData()
        
    


class ContentViewModel: ObservableObject  

    enum State 
        case loading
        case loaded
    

    @Published var state: State = .loading

    func fetchData() 
        state = .loading
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
            self.state = .loaded
        
    

这里是详细视图的代码:

import SwiftUI

struct DetailView: View 

    @StateObject var viewModel = DetailViewModel()

    var body: some View 
        Group 
            switch viewModel.state 
            case .loading:
                Text("Loading...")
            case .loaded:
                HStack 
                    Text("Loaded")
                    Button("Retry") 
                        viewModel.fetchData()
                    
                
            
        
        .onAppear() 
            print("infinite loop here")
            viewModel.fetchData()
        
    


class DetailViewModel: ObservableObject  

    enum State 
        case loading
        case loaded
    

    @Published var state: State = .loading

    func fetchData() 
        state = .loading
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
            self.state = .loaded
        
    

我在这里附上项目: https://www.dropbox.com/s/5alokj3q81jbpj7/TestBug.zip?dl=0

我使用的是 Xcode 版本 12.5.1 (12E507) 和 ios 14.5

非常感谢。

【问题讨论】:

似乎在 iOS 15 上按预期工作 这里一样,用 Xcode 13 beta 5 测试没有任何问题 为合唱添加另一个声音。 13RC、iOS 14.4 和 iOS 15。两者都经过测试。您确定这段代码导致了无限循环吗? 每个人都在评论它是如何在错误的 Xcode 版本上工作的。刚刚测试过,问题如 Xcode 12.5.1 和 iOS 14.5 所述。 好的,我刚刚意识到问题出在 Group 和 xcode 12 上。如果我使用 VStack,它可以工作。所以,这是一个苹果虫。 【参考方案1】:

此问题是带有 Group 的 iOS 14 中的一个错误,即使在使用 Xcode 13 构建时仍会发生。

发生的事情是 SwiftUI 运行时弄乱了它的视图差异,所以它认为它“出现”时实际上只是重新加载,因此没有消失(这是调用 onAppear 的要求,因此错误)。

为了弥补这个问题,您需要使用初始化程序而不是依赖onAppear

我已经修改了你的代码,这个修改后的版本不会无限循环。

struct DetailView: View 

    @StateObject var viewModel = DetailViewModel()

    var body: some View 
        Group 
            switch viewModel.state 
            case .loading:
                Text("Loading...")
            case .loaded:
                HStack 
                    Text("Loaded")
                    Button("Retry") 
                        viewModel.fetchData()
                    
                
            
        
    


class DetailViewModel: ObservableObject  

    enum State 
        case loading
        case loaded
    

    @Published var state: State = .loading

    init() 
        self.fetchData()
    

    func fetchData() 
        state = .loading
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
            self.state = .loaded
        
    

希望项目顺利!

【讨论】:

也许该错误与 Group 有关。我刚刚意识到,如果我使用 VStack 而不是 Group,它可以工作。 我同意您的测试答案,但错误在于 Group(和 Xcode 12)。谢谢! 哦,我完全错过了你在使用一个组!很好,我会更新我的答案:) 感谢您的回答!这也发生在 iOS 15 中,并且在我发现这一点之前已经挣扎了很长时间。【参考方案2】:

我在 iOS 15 中看到了类似的情况,并将所有 onAppear 内容移至初始化程序修复的内容。 没有更多的无限循环试图使对话框出现。奇怪的是,这只发生在特定状态的设置中。

在 onAppear 中,我的代码如下:

useSecondary = data.settings.showSecondaryAxis
...

但在 init 函数中需要这个来完成这项工作:

   init(data: Binding<PlotData> 
      self._data = data
      _useSecondary = State(initialValue: data.wrappedValue.settings.showSecondaryAxis)
... 

… 很麻烦,但很管用。

【讨论】:

以上是关于在 SwiftUI 中使用 onAppear 时出现无限循环的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 视图中结合 onChange 和 onAppear 事件?

SwiftUI解决List子项无法正确触发onAppear和onDisappear事件的问题

SwiftUI解决List子项无法正确触发onAppear和onDisappear事件的问题

SwiftUI 视图 onAppear 事件调用回扫手势动作

SwiftUI:onAppear 的奇怪行为

每次视图出现时,SwiftUI .onAppear withAnimation 都会加快速度。为啥?