Calories
This feature allows to write nutrition data in apple HealthKit container. Use an instance of RookEventsManager class, which includes writeNutritionEvent method to write nutrition data in apple HealthKit and send the data to rook API.
info
To use this method, ensure a user ID is added and nutrition write permission are granted,
use requestDietaryWritePermissions method to request them.
Example
The bellow code shows how to use this functionality.
import Foundation
import RookSDK
final class WriteNutritionEventViewModel: ObservableObject {
private let eventManager: RookEventsManager = RookEventsManager()
var alertMessage: String = ""
@Published var name: String = "Food"
@Published var unit: Int = .zero
@Published var amount: Double = .zero
@Published var dietaryKiloCaloriesEnergyConsumed: Double = .zero
@Published var dietaryMilliLiterWater: Double = .zero
@Published var dietaryGramCarbohydrates: Double = .zero
@Published var dietaryGramFatTotal: Double = .zero
@Published var dietaryGramFatSaturated: Double = .zero
@Published var dietaryGramProtein: Double = .zero
@Published var dietaryMicroGramVitaminA: Double = .zero
@Published var dietaryMilliGramVitaminB6: Double = .zero
@Published var dietaryMilliGramsCalcium: Double = .zero
@Published var dietaryMilliGramsChloride: Double = .zero
@Published var date: Date = Date()
@Published var loading: Bool = false
@Published var showAlert: Bool = false
func writeNutritionEvent() {
Task {
do {
DispatchQueue.main.async {
self.loading = true
self.showAlert = false
}
let event: NutritionInsertionEvent = NutritionInsertionEvent(
name: name,
quantity: NutritionInsertionEventQuantity(
unit: unit,
amount: amount),
date: date,
nutritionInsertionEnergyWaterDataRelated: NutritionInsertionEnergyWaterDataRelated(
dietaryKiloCaloriesEnergyConsumed: dietaryKiloCaloriesEnergyConsumed,
dietaryMilliLiterWater: nil),
nutritionCarbohydratesDataRelated: NutritionCarbohydratesDataRelated(
dietaryGramCarbohydrates: dietaryGramCarbohydrates, dietaryGramFiber: nil, dietaryGramSugar: nil),
nutritionFatsDataRelated: NutritionFatsDataRelated(dietaryGramFatTotal: dietaryGramFatTotal, dietaryGramFatSaturated: dietaryGramFatSaturated, dietaryGramFatMonounsaturated: nil, dietaryGramFatPolyunsaturated: nil),
nutritionProteinDataRelated: NutritionProteinDataRelated(dietaryGramProtein: dietaryGramProtein, dietaryMilliGramCholesterol: nil),
nutritionVitaminsDataRelated: NutritionVitaminsDataRelated(dietaryMicroGramVitaminA: dietaryMicroGramVitaminA, dietaryMilliGramVitaminB6: dietaryMilliGramVitaminB6, dietaryMicroGramVitaminB12: nil, dietaryMilliGramVitaminC: nil, dietaryMicroGramVitaminD: nil, dietaryMilliGramVitaminE: nil, dietaryMicroGramVitaminK: nil, dietaryMilliGramThiamin: nil, dietaryMilliGramRiboflavin: nil, dietaryMilliGramNiacin: nil, dietaryMilliGramPantothenicAcid: nil, dietaryMicroGramFolate: nil, dietaryMicroGramBiotin: nil),
nutritionMineralDataRelated: NutritionMineralDataRelated(dietaryMilliGramsCalcium: dietaryMilliGramsCalcium, dietaryMilliGramsChloride: dietaryMilliGramsChloride, dietaryMilliGramsChromium: nil, dietaryMilliGramsCopper: nil, dietaryMilliGramsIodine: nil, dietaryMilliGramsIron: nil, dietaryMilliGramsMagnesium: nil, dietaryMilliGramsManganese: nil, dietaryMilliGramsMolybdenum: nil, dietaryMilliGramsPhosphorus: nil, dietaryMilliGramsPotassium: nil, dietaryMilliGramsSelenium: nil, dietaryMilliGramsSodium: nil, dietaryMilliGramsZinc: nil))
do {
let result: Bool = try await eventManager.writeNutritionEvent(event: event)
self.alertMessage = result ? "Success" : "Failed"
} catch {
self.alertMessage = error.localizedDescription
}
DispatchQueue.main.async {
self.loading = false
self.showAlert = true
}
}
}
}
}
import SwiftUI
struct WriteNutrtionEventView: View {
@StateObject var viewModel: WriteNutritionEventViewModel = WriteNutritionEventViewModel()
var body: some View {
VStack {
if viewModel.loading {
ProgressView()
} else {
form
.padding(24.0)
.alert("", isPresented: $viewModel.showAlert) {
Button("Ok", action: {
viewModel.showAlert = false
})
} message: {
Text(viewModel.alertMessage)
}
}
}
}
var form: some View {
ScrollView {
Section("Basics") {
TextField("Name", text: $viewModel.name)
.textInputAutocapitalization(.words)
.autocorrectionDisabled(false)
Stepper(value: $viewModel.unit.clamped(min: 0), in: 0...10_000) {
HStack {
Text("Unit")
Spacer()
Text("\(viewModel.unit)")
.foregroundStyle(.secondary)
}
}
HStack {
Text("Amount")
Spacer()
TextField("0", value: $viewModel.amount.clamped(min: 0, maxDecimals: 2), format: .number)
.keyboardType(.decimalPad)
.multilineTextAlignment(.trailing)
.frame(width: 120)
}
Stepper("Adjust amount", value: $viewModel.amount.clamped(min: 0, maxDecimals: 2), step: 0.5)
DatePicker("Date", selection: $viewModel.date, displayedComponents: [.date, .hourAndMinute])
}
Section("Macros (g)") {
numericRow(
title: "Carbohydrates",
value: $viewModel.dietaryGramCarbohydrates.clamped(min: 0, maxDecimals: 2),
unit: "g"
)
numericRow(
title: "Total Fat",
value: $viewModel.dietaryGramFatTotal.clamped(min: 0, maxDecimals: 2),
unit: "g"
)
numericRow(
title: "Saturated Fat",
value: $viewModel.dietaryGramFatSaturated.clamped(min: 0, maxDecimals: 2),
unit: "g"
)
numericRow(
title: "Protein",
value: $viewModel.dietaryGramProtein.clamped(min: 0, maxDecimals: 2),
unit: "g"
)
}
Section("Energy & Hydration") {
numericRow(
title: "Calories",
value: $viewModel.dietaryKiloCaloriesEnergyConsumed.clamped(min: 0, maxDecimals: 0),
unit: "kcal",
keyboard: .numberPad
)
numericRow(
title: "Water",
value: $viewModel.dietaryMilliLiterWater.clamped(min: 0, maxDecimals: 0),
unit: "mL",
keyboard: .numberPad
)
}
Section("Vitamins") {
numericRow(
title: "Vitamin A",
value: $viewModel.dietaryMicroGramVitaminA.clamped(min: 0, maxDecimals: 1),
unit: "µg"
)
numericRow(
title: "Vitamin B6",
value: $viewModel.dietaryMilliGramVitaminB6.clamped(min: 0, maxDecimals: 2),
unit: "mg"
)
}
Section("Minerals") {
numericRow(
title: "Calcium",
value: $viewModel.dietaryMilliGramsCalcium.clamped(min: 0, maxDecimals: 1),
unit: "mg"
)
numericRow(
title: "Chloride",
value: $viewModel.dietaryMilliGramsChloride.clamped(min: 0, maxDecimals: 1),
unit: "mg"
)
}
Section {
Button {
viewModel.writeNutritionEvent()
} label: {
Label("Save", systemImage: "checkmark.circle.fill")
}
}
}
}
private func numericRow(
title: String,
value: Binding<Double>,
unit: String,
keyboard: UIKeyboardType = .decimalPad
) -> some View {
HStack(spacing: 12) {
Text(title)
Spacer()
TextField("0", value: value, format: .number)
.keyboardType(keyboard)
.multilineTextAlignment(.trailing)
.frame(width: 120)
Text(unit)
.foregroundStyle(.secondary)
.frame(minWidth: 34, alignment: .leading)
}
.accessibilityElement(children: .combine)
.accessibilityLabel("\(title) \(unit)")
}
}
private extension Binding where Value == Double {
func clamped(min: Double = 0, maxDecimals: Int = 2) -> Binding<Double> {
Binding(
get: { self.wrappedValue },
set: { newValue in
let factor = pow(10.0, Double(maxDecimals))
let rounded = (newValue * factor).rounded() / factor
self.wrappedValue = Swift.max(min, rounded)
}
)
}
}
private extension Binding where Value == Int {
func clamped(min: Int = 0) -> Binding<Int> {
Binding(
get: { self.wrappedValue },
set: { newValue in
self.wrappedValue = Swift.max(min, newValue)
}
)
}
}