'How to do asynchronous action with SwiftUI button

I want to click a button in SwiftUI that will trigger a JSON encoding action. This action is time consuming thus I need it to be async. I have already tried two solutions but they do not work. One major problem is how to create a async version of the json encoding?

Solution 1)

public func encodeJSON<T>(_ value: T, encoder: JSONEncoder, completionHandler: @escaping (Data?, Error?) -> Void) where T: Encodable {
        DispatchQueue.global().async {
            do {
                let data = try encoder.encode(value)
                DispatchQueue.main.async {
                    completionHandler(data, nil)
                    print("finish encode json")
                }
            } catch {
                DispatchQueue.main.async {
                    completionHandler(nil, error)
                    print("fail encode json")
                }
            }
        }
    }
    
    public func encodeJSON<T>(_ value: T, encoder: JSONEncoder) async throws -> Data where T: Encodable {
        
        try await withUnsafeThrowingContinuation { continuation in
            encodeJSON(value, encoder: encoder) { data, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else if let data = data {
                    continuation.resume(returning: data)
                } else {
                    fatalError()
                }
            }
        }
    }

And I call the function in SwiftUI body:

Button {
                
                let localDate = dailyUploadRecord.date!
                let impactMed = UIImpactFeedbackGenerator(style: .medium)
                impactMed.impactOccurred()
 
                
               
                guard let file = UploadFileManager.shared.fetchedResults else { return }
                
                let encoder = JSONEncoder()
                encoder.dateEncodingStrategy = .millisecondsSince1970
                
                Task {
                    isEncoding = true
                    let result = try await uploadManager.encodeJSON(file, encoder: encoder)
                    print(result)
                    isEncoding = false
                }

            } label: {
                Text(“TEST")
                    .overlay {
                        if isEncoding {
                            ProgressView()
                        }
                    }
            }
            .disabled(isEncoding)
            .buttonStyle(.bordered)

However, it gave me the runtime error: Thread 6: EXC_BREAKPOINT (code=1, subcode=0x1b338b088)

Then, I tried the second solution:

public func encodeJSON<T>(_ value: T, encoder: JSONEncoder) async throws -> Data where T: Encodable {
        return try encoder.encode(value)
    }
Button {
                
            let localDate = dailyUploadRecord.date!
            let impactMed = UIImpactFeedbackGenerator(style: .medium)
            impactMed.impactOccurred()
                
            guard let file = UploadFileManager.shared.fetchedResults else { return }
                
            let encoder = JSONEncoder()
            encoder.dateEncodingStrategy = .millisecondsSince1970
                
            Task {
                isEncoding = true
                let result = try await encodeJSON(file, encoder: encoder)
                print(result)
                isEncoding = false
            }

        } label: {
            Text(“TEST")
                .overlay {
                    if isEncoding {
                        ProgressView()
                    }
                }
        }
        .disabled(isEncoding)
        .buttonStyle(.bordered)

However, the ui is freezed and when the encodeJSON is finished, it return to normal and I can interact with.

My question is: How to create an async version of JSONEncoder().encode(value: Data) and call it in the Button of SwiftUI, without blocking the main thread (make the UI freezed)? Any suggestion is welcomed!

I tried two solutions. One is create a async version from the DispatchQueue.global().async {} and convert it. The other is directly wrap the JSONEncoder().encode(value: Data) in a async function. However, the two solutions did not work.

I expect to click the Button and the related encoding function could execuate asynchronously.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source