Skip to content

SwiftUI Food Logging REST API Integration Guide

Published June 6, 2026

SwiftUI food logging needs a reliable data source behind every tap. This guide covers SwiftUI food logging REST API integration from URLSession calls to a polished search and log flow using Calorie API through your backend proxy.

Architecture

SwiftUI View → NutritionService → your-backend.com → Calorie API
                      ↑
               no API key in IPA

Read hide nutrition API key mobile backend proxy before TestFlight.

Step 1: Define Codable Models

struct FoodSearchResult: Codable, Identifiable {
    let id: Int
    let name: String
    let brand: String?
    let calories: Double?
}

struct FoodSearchResponse: Codable {
    let results: [FoodSearchResult]
    let total: Int
}

Step 2: NutritionService With async/await

enum NutritionError: Error {
    case badURL, badResponse, decoding
}

final class NutritionService {
    private let baseURL: URL

    init(baseURL: URL) {
        self.baseURL = baseURL
    }

    func searchFoods(query: String, limit: Int = 15) async throws -> [FoodSearchResult] {
        guard query.count >= 2 else { return [] }
        var components = URLComponents(url: baseURL.appendingPathComponent("api/foods/search"), resolvingAgainstBaseURL: false)!
        components.queryItems = [
            URLQueryItem(name: "q", value: query),
            URLQueryItem(name: "limit", value: String(limit))
        ]
        guard let url = components.url else { throw NutritionError.badURL }

        let (data, response) = try await URLSession.shared.data(from: url)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw NutritionError.badResponse }
        let decoded = try JSONDecoder().decode(FoodSearchResponse.self, from: data)
        return decoded.results
    }
}

Inject baseURL from a config plist pointing at your Next.js or Firebase proxy.

Step 3: Search ViewModel

@MainActor
final class FoodSearchViewModel: ObservableObject {
    @Published var query = ""
    @Published var results: [FoodSearchResult] = []
    @Published var isLoading = false

    private let service: NutritionService
    private var searchTask: Task<Void, Never>?

    init(service: NutritionService) {
        self.service = service
    }

    func onQueryChange() {
        searchTask?.cancel()
        searchTask = Task {
            try? await Task.sleep(nanoseconds: 300_000_000)
            guard !Task.isCancelled else { return }
            isLoading = true
            defer { isLoading = false }
            do {
                results = try await service.searchFoods(query: query)
            } catch {
                results = []
            }
        }
    }
}

Step 4: SwiftUI Search View

struct FoodSearchView: View {
    @StateObject var viewModel: FoodSearchViewModel

    var body: some View {
        List(viewModel.results) { food in
            VStack(alignment: .leading) {
                Text(food.name).font(.headline)
                if let brand = food.brand { Text(brand).font(.caption) }
                if let cal = food.calories {
                    Text("\(Int(cal)) kcal / 100g").foregroundStyle(.secondary)
                }
            }
        }
        .searchable(text: $viewModel.query)
        .onChange(of: viewModel.query) { _, _ in viewModel.onQueryChange() }
        .overlay { if viewModel.isLoading { ProgressView() } }
    }
}

Step 5: Daily Log State

Store logged entries in SwiftData or Core Data:

struct FoodLogEntry: Identifiable {
    let id: UUID
    let foodId: Int
    let name: String
    let grams: Double
    let calories: Double
    let proteinG: Double
    let carbsG: Double
    let fatG: Double
    let loggedAt: Date
}

Fetch detail on select, scale nutrients, append to today's section.

Step 6: Barcode With AVFoundation

Use DataScannerViewController (iOS 16+) or a third-party scanner. Send UPC to /api/foods/barcode/{upc} on your backend.

Tips for App Store Quality

TipWhy
Debounce 300 msSaves API quota
Cache recentsFaster repeat logs
Empty statesReduces confusion
Haptic on logConfirms action

Start free | Documentation

Frequently Asked Questions

How do I integrate a food API in SwiftUI?

Create Codable models, build a NutritionService with URLSession async/await calling your backend proxy, bind search results to a List with searchable, and scale per-100g nutrients when logging.

Should SwiftUI apps call Calorie API directly?

Only for local debugging. Production iOS apps should proxy through your server so the API key is not embedded in the IPA.

What is the best REST API for SwiftUI food logging?

Calorie API provides search, barcode, and per-100g macros in JSON ideal for SwiftUI Codable models and macro tracking apps.

How do I debounce food search in SwiftUI?

Cancel the previous Task on each query change, sleep 300 ms, then call searchFoods. This prevents one API call per keystroke.

Can I use SwiftData to store food logs?

Yes. Store scaled macros and food metadata locally after fetching detail from Calorie API via your backend proxy.

← Back to all articles

Start building with the Calorie API

Get a free API key and access 4M+ foods with search, barcode lookup, and full macro data.