You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中使用JSONDecoder解析API数据并在View结构体中展示文本的问题

How to Display API Data in SwiftUI Text Component

Let's break down what's going wrong here and fix it step by step—your core issues relate to SwiftUI's state management rules, unhandled JSON decoding edge cases, and improper async operation placement.

Key Problems in Your Current Code

  1. You can't run async API calls directly in SwiftUI's body—the body is a declarative view description that gets re-evaluated frequently, and async callbacks can't directly update view state.
  2. The Ethermine API returns millisecond timestamps (e.g., 1699999999000), but your JSONDecoder doesn't handle this, so decoding Date properties will fail silently.
  3. Your API request's error handling is incomplete—you only print "error" instead of passing failure states to the view.
  4. You're trying to access currentStatistics.unpaid! without properly referencing the API response instance, and force-unwrapping is risky.

Step 1: Fix Date Decoding for the API Response

First, add a custom JSONDecoder to handle the millisecond timestamps from the API:

extension JSONDecoder {
    static let ethermineDecoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .custom { decoder in
            let timestamp = try decoder.singleValueContainer().decode(Int64.self)
            return Date(timeIntervalSince1970: TimeInterval(timestamp) / 1000)
        }
        return decoder
    }()
}

Also, update your struct names to follow Swift's UpperCamelCase convention (for consistency):

struct EthermineResponse: Decodable {
    var status: String
    var data: DataDetails
}

struct DataDetails: Decodable {
    var statistics: [Statistics]
    var workers: [Workers]
    var currentStatistics: CurrentStatistics
    var settings: Settings
}

// Update all nested structs to UpperCamelCase: Statistics, Workers, CurrentStatistics, Settings

Step 2: Use a ViewModel for State Management

Create an ObservableObject view model to handle API requests and manage state (loading, success, error). This keeps your view code clean and adheres to SwiftUI's state rules:

import Foundation
import SwiftUI

enum APIState<T> {
    case idle
    case loading
    case success(T)
    case error(String)
}

class EthermineViewModel: ObservableObject {
    @Published var state: APIState<EthermineResponse> = .idle
    
    func fetchData(for wallet: String) {
        state = .loading
        let request = EthermineRequest(wallet: wallet)
        request.getData { [weak self] result in
            DispatchQueue.main.async { // Always update UI state on the main thread
                switch result {
                case .success(let data):
                    self?.state = .success(data)
                case .failure(let error):
                    let errorMessage = error == .noDataAvailable ? "No data received" : "Failed to parse response"
                    self?.state = .error(errorMessage)
                }
            }
        }
    }
}

// Fix your API request to handle errors properly
struct EthermineRequest {
    let resourceURL: URL
    
    init(wallet: String) {
        let resourceString = "https://api.ethermine.org/miner/\(wallet)/dashboard"
        guard let resourceURL = URL(string: resourceString) else {
            fatalError("Invalid wallet address or API URL")
        }
        self.resourceURL = resourceURL
    }
    
    func getData(completionHandler: @escaping (Result<EthermineResponse, DataError>) -> Void) {
        let task = URLSession.shared.dataTask(with: resourceURL) { data, _, _ in
            guard let jsonData = data else {
                completionHandler(.failure(.noDataAvailable))
                return
            }
            
            do {
                let ethData = try JSONDecoder.ethermineDecoder.decode(EthermineResponse.self, from: jsonData)
                completionHandler(.success(ethData))
            } catch {
                print("Decoding error: \(error)")
                completionHandler(.failure(.canNotProcessData))
            }
        }
        task.resume()
    }
}

enum DataError: Error {
    case noDataAvailable
    case canNotProcessData
}

Step 3: Update Your Main View to Display the Data

Now use the view model in your main view to fetch and display the unpaid value, handling all possible states:

struct MainView: View {
    let wallet = "b5404f020334f52b33012af3587e69305eabee2c"
    @StateObject private var viewModel = EthermineViewModel()
    
    var body: some View {
        VStack(spacing: 20) {
            switch viewModel.state {
            case .idle:
                Text("Tap to load data")
                    .onTapGesture { viewModel.fetchData(for: wallet) }
            case .loading:
                ProgressView("Loading balance...")
            case .success(let response):
                Text("Unpaid Balance: \(response.data.currentStatistics.unpaid, specifier: "%.6f") ETH")
                    .font(.title)
            case .error(let message):
                Text("Error: \(message)")
                    .foregroundColor(.red)
            }
            
            Spacer()
        }
        .padding()
        .onAppear { viewModel.fetchData(for: wallet) } // Auto-load on view appear
    }
}

Why This Works

  • State Management: @StateObject and @Published ensure the view automatically updates when the API state changes.
  • Main Thread Safety: All state updates happen on the main thread, which is required for SwiftUI view changes.
  • Safe Data Handling: We use a switch statement to handle all possible API states (no force-unwrapping!).
  • Proper Error Handling: Users get clear feedback if something goes wrong with the API request.

内容的提问来源于stack exchange,提问作者DomTorreto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 12:39:09