Search code examples
iosswiftscrollviewswiftui

SwiftUI ScrollView doesn't show fetched results from an ObservableObject


I am trying to fetch data from an api by clicking on a 'Fetch' button and show them via an ForEach loop in a ScrollView.

I am using MVVM Model. The fetch itself happens in an ObservableObject class.

Unfortunately the ScrollView does not show the content. If I am using a List instead of a ScrollView it works fine.

Do you have any idea what I am missing here?

Thank you a lot for your help!

import SwiftUI

struct Photo: Identifiable, Decodable {

    let id = UUID()
    let title: String
}

class ContentViewModel: ObservableObject {

    let api = "https://jsonplaceholder.typicode.com/photos"

    @Published var photos: [Photo] = []

    func fetchData() {
        print("Fetching started")
        guard let url = URL(string: api) else { return }

        URLSession.shared.dataTask(with: url) { data, _, _ in
            DispatchQueue.main.async {
                self.photos = try! JSONDecoder().decode([Photo].self, from: data!)
                print("Fetching successfull. Fetched \(self.photos.count) photos.")
            }
        }.resume()
    }
}

struct ContentView: View {

    @ObservedObject var contentVM = ContentViewModel()

    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(self.contentVM.photos) { photo in
                    Text(photo.title)
                }
            }
            .navigationBarTitle("Home")
            .navigationBarItems(trailing: Button(action: {

                self.contentVM.fetchData()

                }, label: {
                    Text("Fetch")
            }))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Solution

  • You didn't miss anything, the problem is with renders content, I think. even in your example data displays on real device (iPhone 7 iOS 13.1.1), but with long delay. Try with less content or help ScrollView with alignment. I tried this - on device works faster, but only on device:

    class ContentViewModel: ObservableObject {
    
        let api = "https://jsonplaceholder.typicode.com/photos"
    
        @Published var photos: [Photo] = []
        @Published var first100Photos: [Photo] = []
    
        func fetchData() {
            print("Fetching started")
            guard let url = URL(string: api) else { return }
    
            URLSession.shared.dataTask(with: url) { data, _, _ in
                DispatchQueue.main.async {
                    self.photos = try! JSONDecoder().decode([Photo].self, from: data!)
                    for index in 0...100 {
                        self.first100Photos.append(self.photos[index])
                    }
                    print("Fetching successfull. Fetched \(self.photos.count) photos.")
                }
            }.resume()
        }
    }
    
    struct ContentView: View {
    
        @ObservedObject var contentVM = ContentViewModel()
    
        var body: some View {
            NavigationView {
                ScrollView(.vertical) {
                    VStack {
                        ForEach(self.contentVM.first100Photos) { photo in
                            Text(photo.title)
                        }
                    }
    
                }
                .navigationBarTitle("Home")
                .navigationBarItems(trailing: Button(action: {
    
                    self.contentVM.fetchData()
    
                    }, label: {
                        Text("Fetch")
                }))
            }
        }
    }