2

A SwiftUI TabView contains to two tabs. Each displays data from the same model. If the data is missing, a ProgressView is displayed, if the data is present, the current value is displayed. In the example model the data is set asynchronously after 2 seconds to cause the problem. (In my real app, the data is set as a result of an async call.)

After running the app, the first tab is displayed containing the ProgressView and the data is displayed after 2 seconds. So far everything seems to work. When switching from the first Tab to the second the app crashes somewhere 75 levels deep in SwiftUI display code:

(AG::Graph::add_indirect_attribute:) with EXC_BAD_ACCESS.

What am I doing wrong and how can I circumvent the problem, if it is an SwiftUI Bug?

The following code causes the crash:

import SwiftUI
import Combine

class Model: ObservableObject{
    @Published var value:Double?
    
    init(){
        DispatchQueue.main.asyncAfter(deadline: .now() + 3){
            self.value = 2.0
            print("value set")
        }
    }
}

@main
struct TabViewUpdatingTestApp: App {
    @StateObject var model = Model()
    
    var body: some Scene {
        WindowGroup {
            ContentView(model: model)
        }
    }
}

struct Tab: View {
    let name:String
    @ObservedObject var model:Model
    
    var body: some View {
        if let mvalue = model.value{
            Text("name: \(name), value: \(mvalue)")
        }else{
            ProgressView().font(.largeTitle)
        }
    }
}

struct ContentView: View {
    @ObservedObject var model:Model
    
    var body: some View {
        TabView {
            Tab(name: "Tab 1", model: model)
                .tabItem {
                    Label("Tab 1", systemImage: "1.circle")
                }
            Tab(name: "Tab 2", model: model)
                .tabItem {
                    Label("Tab 2", systemImage: "2.circle")
                }
        }
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209

1 Answers1

1

It looks like a bug to me. A possible workaround may be to force-refresh the TabView:

struct ContentView: View {
    @ObservedObject var model: Model

    var body: some View {
        TabView {
            Tab(name: "Tab 1", model: model)
                .tabItem {
                    Label("Tab 1", systemImage: "1.circle")
                }
            Tab(name: "Tab 2", model: model)
                .tabItem {
                    Label("Tab 2", systemImage: "2.circle")
                }
        }
        .id(model.value) // add here
    }
}

Alternatively, instead of redrawing the view when model.value changes, you can create some other variable that changes only once (so you don't refresh the TabView every time but only once at the beginning).

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • Thanks! This prevents the crash from occurring in my real code and since this value is only set once there is no danger of too many redraws. Additionally I didn't know about the id modifier. Could be usefull in the future to do some forced redraws. – SwiftApprentice Jan 21 '21 at 06:25