UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Custom picker for an remote list items

Forums > SwiftUI

Hello, I'm trying to figure out how to wireup following UI

Here is the picker, aka my attempt to have a custom one for items that are stored remotely with search and refresh features:

import SwiftUI
import Combine

struct CustomPicker: View {
    @State var loading = false
    @State var item: CustomPickerItem?
    @State var items: [CustomPickerItem] = []
    @StateObject private var search = DebouncedState(initialValue: "")

    var body: some View {
        List(selection: $item) {
            ForEach(items) { item in
                Text(item.name).tag(item)
            }
        }
        .overlay {
            if loading {
                ContentUnavailableView("Loading", systemImage: "arrow.down.circle.dotted", description: Text("Retrieving items list"))
            }
            else if items.isEmpty {
                ContentUnavailableView("Empty", systemImage: "doc.text.magnifyingglass", description: Text("Empty list retrieved"))
            }
        }
        .task { await load() }
        .refreshable { await load() }
        .searchable(text: $search.currentValue)
        .onChange(of: search.debouncedValue, { oldValue, newValue in
            Task { await load() }
        })
        .onChange(of: item, { oldValue, newValue in
            guard let name = newValue?.name else { return }
            print("Selected \(name)")
        })
    }

    func load() async {
        loading = true
        item = nil
        // pretend we are retrieving list from backend
        let sleepSeconds = 2
        try? await Task.sleep(nanoseconds: UInt64(sleepSeconds) * NSEC_PER_SEC)
        var items = (1...20).map { num in
            let name = "Item \(num)"
            return CustomPickerItem(id: "\(num)", name: name)
        }

        if search.debouncedValue != "" {
            items = items.filter { $0.name.localizedStandardContains(search.debouncedValue) }
        }

        self.items = items
        loading = false
    }
}

private class DebouncedState<Value>: ObservableObject {
    @Published var currentValue: Value
    @Published var debouncedValue: Value

    init(initialValue: Value, delay: Double = 0.3) {
        _currentValue = Published(initialValue: initialValue)
        _debouncedValue = Published(initialValue: initialValue)
        $currentValue
            .debounce(for: .seconds(delay), scheduler: RunLoop.main)
            .assign(to: &$debouncedValue)
    }
}

struct CustomPickerItem: Codable, Identifiable, Hashable {
    let id: String
    let name: String
}

#Preview {
    NavigationStack {
        CustomPicker()
    }
}

And the question is how to wire it up to the form view? aka:

import SwiftUI

struct CustomForm: View {
    @State private var number = 1
    @State private var numbers = [1,2,3]
    @State private var item: CustomPickerItem?

    var body: some View {
        Form {
            Section("Default") {
                Picker("Number", selection: $number) {
                    ForEach(numbers, id: \.self) { number in
                        Text("\(number)")
                    }
                }
                .pickerStyle(.navigationLink)
            }

            Section("Custom") {
                NavigationLink {
                    CustomPicker()
                    .onSubmit {
                        print("Submitted?")
                    }
                } label: {
                    HStack {
                        Text("Custom")
                        Spacer()
                        Text(item?.name ?? "N/A").foregroundStyle(.secondary)
                    }
                }
            }

        }
        .navigationTitle("Pickers")
    }
}

#Preview {
    NavigationStack {
        CustomForm()
    }
}

Wondering if someone can point me the direction how to handle list selection in parent view and closing picker, or may be this one is bad approach in general and should be done differently

   

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.