// // AsyncToggle.swift // TuskerComponents // // Created by Shadowfacts on 4/7/24. // Copyright © 2024 Shadowfacts. All rights reserved. // import SwiftUI public struct AsyncToggle: View { let titleKey: LocalizedStringKey #if !os(visionOS) @available(iOS, obsoleted: 16.0, message: "Switch to LabeledContent") let labelHidden: Bool #endif @Binding var mode: Mode let onChange: (Bool) async -> Bool public init(_ titleKey: LocalizedStringKey, labelHidden: Bool = false, mode: Binding, onChange: @escaping (Bool) async -> Bool) { self.titleKey = titleKey #if !os(visionOS) self.labelHidden = labelHidden #endif self._mode = mode self.onChange = onChange } public var body: some View { #if os(visionOS) LabeledContent(titleKey) { toggleOrSpinner } #else if #available(iOS 16.0, *) { LabeledContent(titleKey) { toggleOrSpinner } } else if labelHidden { toggleOrSpinner } else { HStack { Text(titleKey) Spacer() toggleOrSpinner } } #endif } @ViewBuilder private var toggleOrSpinner: some View { ZStack { Toggle(titleKey, isOn: Binding { mode == .on } set: { newValue in mode = .loading Task { let operationCompleted = await onChange(newValue) if operationCompleted { mode = newValue ? .on : .off } else { mode = newValue ? .off : .on } } }) .labelsHidden() .opacity(mode == .loading ? 0 : 1) if mode == .loading { ProgressView() } } } public enum Mode { case off case loading case on } } #Preview { @State var mode = AsyncToggle.Mode.on return AsyncToggle("", mode: $mode) { _ in try! await Task.sleep(nanoseconds: NSEC_PER_SEC) return true } }