SwiftUI conditional doesn't work at top level in view

I have a view model that looks like this:

class SegmentViewModel: ObservableObject {
    
    @Published private(set) var itemIds = [Int]() 
    private var cancellables: Set<AnyCancellable> = []

    init() {
        fetchIds()
    }

    private func fetchIds() {
        let request = URLRequest(path: "/ids")
        network
           .send(request)
           .receive(on: DispatchQueue.main)
           .sink { completion in
               switch completion {
               case .failure(let error): print(error.localizedDescription)
               case .finished: break
               }
        } receiveValue: { response in
            self.itemIds = response
        }
        .store(in: &cancellables)
    }

}

That all works fine and then my View is as follows:

struct SegmentView: View {
    
    @ObservedObject var viewModel: SegmentViewModel
    
    var body: some View {
        if viewModel.itemIds.isEmpty {
            ProgressView()
        } else {
            ScrollView {
                LazyVStack {
                    ForEach(viewModel.itemIds, id: \.self) { id in
                        ItemView(viewModel: ItemViewModel(itemId: id))
                            .cornerRadius(15)
                            .padding()
                            .shadow(radius: 3)
                    }
                }
            }
        }
    }
    
}

But this doesn't work. The ProgressView is never hidden and replaced with the ScrollView. However, if I change my view to:

struct SegmentView: View {
    
    @ObservedObject var viewModel: SegmentViewModel
    
    var body: some View {
        ScrollView {
            if viewModel.itemIds.isEmpty {
                ProgressView()
            } else {
                LazyVStack {
                    ForEach(viewModel.itemIds, id: \.self) { id in
                        ItemView(viewModel: ItemViewModel(itemId: id))
                            .cornerRadius(15)
                            .padding()
                            .shadow(radius: 3)
                    }
                }
            }
        }
    }
    
}

Then it works as expected and hides the ProgressView once the itemIds aren't empty.
Why is this?

1 answer

  • answered 2020-07-29 17:30 Asperi

    It looks like ViewBuidler consumed the condition and so it is not observed. I would wrap top at Group

    struct SegmentView: View {
        
        @ObservedObject var viewModel: SegmentViewModel
        
        var body: some View {
          Group {               // << here !!
            if viewModel.itemIds.isEmpty {
                ProgressView()
            } else {
                ScrollView {
                    LazyVStack {
                        ForEach(viewModel.itemIds, id: \.self) { id in
                            ItemView(viewModel: ItemViewModel(itemId: id))
                                .cornerRadius(15)
                                .padding()
                                .shadow(radius: 3)
                        }
                    }
                }
            }
          }
        }
    }