Post

Replies

Boosts

Views

Activity

Reply to cycling workouts
I think I found the solution. I think it was just a matter of adding a distance sample. Here is a bunch of HealthKit-related code that might help others with similar issues: import HealthKit class HealthKitManager: ObservableObject {     private let store = HKHealthStore()     func addWorkout(         workoutType: String,         startTime: Date,         endTime: Date,         distance: Double,         calories: Int     ) async throws {         guard let activityType = activityMap[workoutType] else {             throw "no activity type found for \(workoutType)"         }         let preferKM = UserDefaults.standard.bool(forKey: "preferKilometers")         let unit: HKUnit = preferKM ? HKUnit.meter() : HKUnit.mile()         let unitDistance = !distanceWorkouts.contains(workoutType) ?             0 :             (preferKM ? distance * 1000 : distance)         let distanceQuantity = HKQuantity(             unit: unit,             doubleValue: unitDistance         )         let energyBurnedQuantity = HKQuantity(             unit: .largeCalorie(),             doubleValue: Double(calories)         )         let workout = HKWorkout(             activityType: activityType,             start: startTime,             end: endTime,             duration: 0, // compute from start and end data             totalEnergyBurned: energyBurnedQuantity,             totalDistance: distanceQuantity,             metadata: nil         )         try await store.save(workout)         let distanceType = HKObjectType.quantityType(             forIdentifier: .distanceCycling         )!         let distanceSample = HKQuantitySample(             type: distanceType,             quantity: distanceQuantity,             start: startTime,             end: endTime         )         let energyBurnedType = HKObjectType.quantityType(             forIdentifier: .activeEnergyBurned         )!         let energySample = HKQuantitySample(             type: energyBurnedType,             quantity: energyBurnedQuantity,             start: startTime,             end: endTime         )         try await store.addSamples([distanceSample, energySample], to: workout)     }     func authorize(identifiers: [HKQuantityTypeIdentifier]) async throws {         let readSet: Set<HKSampleType> = Set(             identifiers.map { .quantityType(forIdentifier: $0)! }         )         let writeSet: Set<HKSampleType> = [             .quantityType(                 forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned             )!,             .quantityType(                 forIdentifier: HKQuantityTypeIdentifier.distanceCycling             )!,             HKWorkoutType.workoutType()         ]         try await store.requestAuthorization(toShare: writeSet, read: readSet)     }     func average(         identifier: HKQuantityTypeIdentifier,         unit: HKUnit,         startDate: Date,         endDate: Date     ) async throws -> Double {         try await withCheckedThrowingContinuation { completion in             let quantityType = HKQuantityType.quantityType(                 forIdentifier: identifier             )!             let predicate: NSPredicate? = HKQuery.predicateForSamples(                 withStart: startDate,                 end: endDate             )             let query = HKStatisticsQuery(                 quantityType: quantityType,                 quantitySamplePredicate: predicate,                 options: .discreteAverage             ) { (_: HKStatisticsQuery, result: HKStatistics?, error: Error?) in                 if let error {                     completion.resume(throwing: error)                 } else {                     let quantity: HKQuantity? = result?.averageQuantity()                     let result = quantity?.doubleValue(for: unit)                     completion.resume(returning: result ?? 0)                 }             }             store.execute(query)         }     }     func sum(         identifier: HKQuantityTypeIdentifier,         unit: HKUnit,         startDate: Date,         endDate: Date     ) async throws -> Double {         try await withCheckedThrowingContinuation { completion in             let quantityType = HKQuantityType.quantityType(                 forIdentifier: identifier             )!             let predicate: NSPredicate? = HKQuery.predicateForSamples(                 withStart: startDate,                 end: endDate             )             let query = HKStatisticsQuery(                 quantityType: quantityType,                 quantitySamplePredicate: predicate,                 options: .cumulativeSum             ) { (_: HKStatisticsQuery, result: HKStatistics?, error: Error?) in                 if let error {                     completion.resume(throwing: error)                 } else {                     let quantity: HKQuantity? = result?.sumQuantity()                     let result = quantity?.doubleValue(for: unit)                     completion.resume(returning: result ?? 0)                 }             }             store.execute(query)         }     } }
Feb ’23
Reply to HealthKit HKWorkout totalEnergyBurned
It turns out I needed to add a sample to the workout as shown below. But see the comments in the code below about an issue with using the async version of the add method. let energyBurned = HKQuantity( unit: .largeCalorie(), doubleValue: Double(calories) ) let workout = HKWorkout( activityType: HKWorkoutActivityType.cycling, start: startTime, end: endTime, duration: 0, // compute from start and end data totalEnergyBurned: energyBurned, totalDistance: HKQuantity(unit: .mile(), doubleValue: distance), metadata: nil ) try await store.save(workout) let energyBurnedType = HKObjectType.quantityType( forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned )! let sample = HKQuantitySample( type: energyBurnedType, quantity: energyBurned, start: startTime, end: endTime ) // Says "Missing argument for parameter 'completion' in call". // await store.add([sample], to: workout) // Says "Consider using asynchronous alternative version", // but I tried that above and it doesn't work! store.add([sample], to: workout) { _, error in if let error { print("error adding sample:", error) } }
Feb ’23
Reply to StoreKit 2 in-app purchase restore
You recommended watching the video "Implementing proactive in-app purchase restore". I've already watched that a few times. I think I'm doing exactly what it recommends and it is not enough to get my app past App Review. My in-app purchase is the simplest possible case. It is a one-time, non-consumable purchase that simply unlocks some functionality in my app. The part of my code that enables making the purchase and unlocking functionality is working perfectly. I just don't have the part to restore previous purchases working. I bet the solution is less than 10 lines of code. It's maddening that I can't find a working example online.
Sep ’22
Reply to update TabView background color
I landed on a solution where I set the background color of the TabView to clear with this:         UITabBar.appearance().backgroundColor = .clear Then I added a background color that is the color I want for the entire screen, including the TabView with this:         ZStack {             Color.fromJSON(backgroundColor).ignoresSafeArea()             VStack(alignment: .leading) {                 content()                     .padding(.horizontal)                 Spacer() // pushes content to top             }         } Of course one could position a Color behind only the tab bar to change the background color of just that part of the screen.
Aug ’22
Reply to update TabView background color
I would prefer to use a SwiftUI only approach if there is one. If not, I prefer to use UIKit in the most natural way I can. What would you recommend if I can't require iOS 16 yet? The key requirement is that I need to be able to set the tab bar background color when the app starts AND change it later based on user interaction. Is the answer that it can't be done without iOS 16?
Jul ’22
Reply to Invalid Binary
I am hitting the same situation. I understand why it is happening. In my project "Build Settings" I have "Supported Platforms" set to "iOS". But if I change it to "watchOS" then my app no longer builds. I don't know what changes I need to make in my project settings to produce a watchOS binary. I could use suggestions.
Jul ’22