#swift #swiftui #alamofire
#быстрый #свифтуи #аламофайр
Вопрос:
Я пытаюсь создать индикатор выполнения загрузки и показывать предупреждение одновременно с завершением загрузки.
Для этой задачи я использую AlamoFire с SwiftUI, так как это упрощает загрузку. Однако, когда я отслеживаю прогресс с помощью progressView с опубликованной переменной, весь пользовательский интерфейс блокируется, и я не могу понять, как это исправить.
Я попытался добавить программу загрузки в отдельную очередь отправки, но мне все равно нужно обновить пользовательский интерфейс из основного потока, иначе Xcode будет жаловаться.
Как протестировать прилагаемый пример кода:
- Нажмите «Начать загрузку».
- Подождите, пока представление прогресса немного сдвинется
- Нажмите кнопку «Показать предупреждение» .
- Попробуйте закрыть оповещение, оно не закроется.
Я был бы признателен за любую помощь.
импорт SwiftUI импорт Alamofire
struct ContentView: View { @StateObject var viewModel: ViewModel = ViewModel() @State private var showAlert = false var body: some View { VStack { Button("Show Alert") { showAlert.toggle() } Button("Start download") { viewModel.startDownload() } if viewModel.showProgressView { ProgressView("Downloading…", value: viewModel.downloadProgress, total: 1.0) .progressViewStyle(.linear) } } .alert(isPresented: $showAlert) { Alert( title: Text("Text"), dismissButton: .cancel() ) } } } class ViewModel: ObservableObject { @Published var currentDownload: DownloadRequest? = nil @Published var downloadProgress: Double = 0.0 @Published var showProgressView: Bool = false func startDownload() { print("Function called!") showProgressView.toggle() let queue = DispatchQueue(label: "alamofire", qos: .utility) let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory) AF.download("https://speed.hetzner.de/10GB.bin", to: destination) .downloadProgress(queue: queue) { progress in print(progress.fractionCompleted) DispatchQueue.main.async { self.downloadProgress = progress.fractionCompleted } } .response { response in print(response) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Ответ №1:
Проблема здесь в том, что вы эффективно отправляете спам в поток пользовательского интерфейса с обновлениями, так downloadProgress
как alamofire очень часто вызывает закрытие, предоставляемое (посмотрите на отпечатки консоли). Вам нужно немного изменить ход обновления с AF, чтобы можно было зарегистрировать нажатие кнопки для отмены предупреждения (в Сочетании это будет называться отменой). То, что я сделал здесь, добавлено немного счетчика времени, чтобы он обновлял прогресс каждую 1
секунду. Время между этими обновлениями позволяет потоку пользовательского интерфейса свободно реагировать на нажатия и т. Д.
import SwiftUI import Alamofire struct ContentView: View { @StateObject var viewModel: ViewModel = ViewModel() @State private var showAlert = false var body: some View { VStack { Button("Show Alert") { showAlert.toggle() } Button("Start download") { viewModel.startDownload() } if viewModel.showProgressView { ProgressView("Downloading…", value: viewModel.downloadProgress, total: 1.0) .progressViewStyle(.linear) } } .alert(isPresented: $showAlert) { Alert( title: Text("Text"), dismissButton: .cancel() ) } } } class ViewModel: ObservableObject { @Published var currentDownload: DownloadRequest? = nil @Published var downloadProgress: Double = 0.0 @Published var showProgressView: Bool = false func startDownload() { print("Function called!") showProgressView.toggle() let queue = DispatchQueue(label: "net", qos: .userInitiated) let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory) var last = Date() AF.download("https://speed.hetzner.de/10GB.bin", to: destination) .downloadProgress(queue:queue) { progress in print(progress.fractionCompleted) if Date().timeIntervalSince(last) gt; 1 { last = Date() DispatchQueue.main.async { self.downloadProgress = progress.fractionCompleted } } } .response { response in // print(response) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }