swift 加载时从JSON加载的滚动列表

k2fxgqgv  于 2023-06-28  发布在  Swift
关注(0)|答案(2)|浏览(101)

我是SwiftUI的新手,试图从本地保存的JSON文件加载列表。我可以显示json列表,但我想将其滚动到列表中的特定索引。以下是我在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)
        }
    }
}

我的演示者文件:

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
        }
    }
}

我试过onAppear和onChange,但都不管用。

mctunoxg

mctunoxg1#

尝试这种方法,在ScrollView之外使用ScrollViewReader,在ScrollView之外使用.onChange(...),如示例代码所示。
正如我在评论中所说的,在class CountryListPresenter: ObservableObject中使用@Published var countries = [CountryInfoValue]()。另请注意,声明struct CountryInfoValue: Identifiable,不要在ForEach循环中使用,id: \.self
下面的例子适用于我:

struct ContentView: View {
    var body: some View {
        SignUpEnterPhoneNumberView()
    }
}

struct SignUpEnterPhoneNumberView: View {
    @StateObject private var presenter = CountryListPresenter()
    @State private var searchText = ""
    
    var body: some View {
        VStack {
            NavigationStack {
                // for testing
                Button {
                    // to trigger onChange
                    presenter.countries.append(CountryInfoValue(id: presenter.countries.count))
                } label: {
                    Text("go to 30")
                }
                ScrollViewReader { proxy in
                    ScrollView {
                        ForEach(presenter.countries) { country in  // <--- here, no id: \.self
                            HStack(spacing: 10) {
                                Text("\(country.id)")
                                Text(country.emoji ?? "")
                                Text(country.phone)
                                Text(country.name)
                                Spacer()
                            }
                        }
                    }
                    .onChange(of: presenter.countries.count) { _ in
                        withAnimation {
                            proxy.scrollTo(30)  // <--- here
                        }
                    }
                    .navigationBarTitle(Text("Select Country Code"), displayMode: .inline)
                    Spacer()
                }
                .searchable(text: $searchText, prompt: presenter.searchTitle)
            }
        }
    }
}

struct CountryInfoValue: Identifiable, Hashable {  // <-- here, Identifiable
    let id: Int
    var emoji: String? = String(UUID().uuidString.prefix(4))
    var phone = String(UUID().uuidString.prefix(5))
    var name = String(UUID().uuidString.prefix(6))
}

class CountryListPresenter: ObservableObject {
    let navigationTitle: String
    let searchTitle: String
    @Published var countries = [CountryInfoValue]() // <-- here
    var selectedIndex = 50
    
    init() {
        navigationTitle = "Select Country Code"
        searchTitle = "Search"
        fetchCountryList()
    }
    
    func fetchCountryList() {
        // for testing
        for index in 0...40 {
            countries.append(CountryInfoValue(id: index))
        }
    }
}

编辑-1:
您可以轻松地使用.onAppear{...}而不是Button...scroll to a row on view load。下面是对我有用的代码:

struct SignUpEnterPhoneNumberView: View {
    @StateObject private var presenter = CountryListPresenter()
    @State private var searchText = ""

    var body: some View {
        VStack {
            NavigationStack {
                ScrollViewReader { proxy in
                    ScrollView {
                        ForEach(presenter.countries) { country in
                            HStack(spacing: 10) {
                                Text("\(country.id)")
                                Text(country.emoji ?? "")
                                Text(country.phone)
                                Text(country.name)
                                Spacer()
                            }
                        }
                    }
                    .onAppear {
                        withAnimation {
                            // can also look into `.debounce(...)`
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                                proxy.scrollTo(30)
                            }
                        }
                    }
                    .navigationBarTitle(Text("Select Country Code"), displayMode: .inline)
                    Spacer()
                }
                .searchable(text: $searchText, prompt: presenter.searchTitle)
            }
        }
    }
}
wlzqhblo

wlzqhblo2#

请尝试将此声明为

@Published var countries = [CountryInfoValue]()

如果你想监听ObservableObject类的字段的变化,你应该声明它们@Published,这样当它的值改变时,它们就可以更新监听器。
我相信,因为它没有公布其空状态是复制到国家。你不能滚动到项目,因为它是空的。
下面的代码按预期工作。我试图复制你的逻辑。

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)
        }
    }
    
}

相关问题