// // AsyncPicker.swift // TuskerComponents // // Created by Shadowfacts on 4/9/24. // import SwiftUI public struct AsyncPicker: View { let titleKey: LocalizedStringKey @available(iOS, obsoleted: 16.0, message: "Switch to LabeledContent") let labelHidden: Bool let alignment: Alignment @Binding var value: V let onChange: (V) async -> Bool let content: Content @State private var isLoading = false public init(_ titleKey: LocalizedStringKey, labelHidden: Bool = false, alignment: Alignment = .center, value: Binding, onChange: @escaping (V) async -> Bool, @ViewBuilder content: () -> Content) { self.titleKey = titleKey self.labelHidden = labelHidden self.alignment = alignment self._value = value self.onChange = onChange self.content = content() } public var body: some View { if #available(iOS 16.0, *) { LabeledContent(titleKey) { picker } } else if labelHidden { picker } else { HStack { Text(titleKey) Spacer() picker } } } private var picker: some View { ZStack(alignment: alignment) { Picker(titleKey, selection: Binding(get: { value }, set: { newValue in let oldValue = value value = newValue isLoading = true Task { let operationCompleted = await onChange(newValue) if !operationCompleted { value = oldValue } isLoading = false } })) { content } .labelsHidden() .opacity(isLoading ? 0 : 1) if isLoading { ProgressView() } } } } #Preview { @State var value = 0 return AsyncPicker("", value: $value) { _ in try! await Task.sleep(nanoseconds: NSEC_PER_SEC) return true } content: { ForEach(0..<10) { Text("\($0)").tag($0) } } }