Search code examples
swiftswiftuiscrollview

Scroll list loaded from json on load


I am new to SwiftUI and trying to load a list from a json file which is locally saved. I am able display the json list but I want to scroll it to a specific index in the list. Following is my code in View:

    struct SignUpEnterPhoneNumberView: View {

    @StateObject private var presenter = CountryListPresenter()
    @State private var searchText = ""
    
    var body: some View {
        VStack {
            NavigationStack {
                ScrollView {
                    ScrollViewReader { proxy in
                        ForEach(presenter.countries, id: \.self) { country in
                            HStack(spacing: 10) {
                                Text(country.emoji ?? "")
                                Text(country.phone)
                                Text(country.name)
                                Spacer()
                            }
                        }
                        .onChange(of: presenter.countries.count, perform: { _ in
                            proxy.scrollTo(30)
                        })
                    }
                }
                .navigationBarTitle(Text("Select Country Code"), displayMode: .inline)
                Spacer()
            }
            .searchable(text: $searchText, prompt: presenter.searchTitle)
        }
    }
}

My presenter file:

    class CountryListPresenter: ObservableObject {
    let navigationTitle: String
    let searchTitle: String
    var countries = [CountryInfoValue]()
    var selectedIndex = 50
    init(
    ) {
        navigationTitle = "Select Country Code"
        searchTitle = "Search"
        fetchCountryList()
    }
    
    func fetchCountryList() {
        guard let url = Bundle.main.url(forResource: "countries.emoji", withExtension: "json") else {
            return
        }
        let data = try? Data(contentsOf: url)
        if let data = data {
            let countryInfo = try? JSONDecoder().decode(CountryInfo.self, from: data)
            var countryList = countryInfo?.map { $0.value }
            countryList = countryList?.sorted(by: { $0.name < $1.name })
            guard let countryList = countryList else {
                return
            }
            countries = countryList
        }
    }
}

I have tried both onAppear and onChange but nothing works for me.


Solution

  • Please try to declare this as

    
    @Published var countries = [CountryInfoValue]()
    
    

    If you want to listen to changes of field of ObservableObject class you should declare them @Published so they can update listeners when its value changed.

    I believe since its not published its empty state is copied to state. And you cant scrollTo item because its empty.

    Code below works as intented. I tried to copy your logic there.

    struct ContentView: View {
        @StateObject var viewModel = SomeViewMode()
        var body: some View {
            ScrollViewReader { proxy in
                ScrollView {
                    ForEach($viewModel.items.indices,id:\.self) { index in
                        ScrollItem()
                            .onAppear {
                                proxy.scrollTo(30)
                            }
                    }
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    
    
    
    struct ScrollItem: View {
        var body: some View {
            Rectangle()
                .fill(Color.red)
                .frame(minHeight: 300)
        }
    }
    
    
    
    class SomeViewMode : ObservableObject {
        
       @Published var items = [Int]()
        
        init() {
            DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
                self?.items = Array(repeating:Int.random(in: 0..<1000), count: 50)
            }
        }
        
    }