feat(healthkit): get stepLength and calculate how many steps it takes to get somewhere

This commit is contained in:
2024-11-27 14:37:30 +01:00
parent 08a5693ae1
commit 448bad5c96
7 changed files with 114 additions and 23 deletions

View File

@@ -249,12 +249,14 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = StepMap/StepMap.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"StepMap/Preview Content\"";
DEVELOPMENT_TEAM = SSJBLTMP95;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHealthShareUsageDescription = "We need to access your step length and walking speed to calculate metrics tailored to you";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "We need to access your location to set starting point of your route";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "We need to access your location to set starting point of your route";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -280,12 +282,14 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = StepMap/StepMap.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"StepMap/Preview Content\"";
DEVELOPMENT_TEAM = SSJBLTMP95;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHealthShareUsageDescription = "We need to access your step length and walking speed to calculate metrics tailored to you";
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "We need to access your location to set starting point of your route";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "We need to access your location to set starting point of your route";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;

View File

@@ -6,20 +6,25 @@
//
import CoreLocation
import HealthKitUI
import MapKit
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
@StateObject var locationManager = LocationManager()
@StateObject var healthKitManager = HealthKitManager()
@State private var position = MapCameraPosition.automatic
@State private var showSearch: Bool = true
@State private var directions: [MKRoute] = []
@State var healthKitAccess = false
@State var stepLength: Double?
// TODO: create a map
// Add navigation to the map
// after you click the navigation button, show the start and end place on the map with a tag or whatever it's called
// FIX: calling the directions too many times, wait till user is finished typing
// add "cancel" button that will hide the route
// add ability to hold on the map to place a mark (end goal)
// Display the calculated distance and how long will it take by walking
@@ -38,11 +43,15 @@ struct ContentView: View {
.sheet(
isPresented: $showSearch,
content: {
SearchView(directions: $directions, locationManager: locationManager)
SearchView(
directions: $directions, stepLength: $stepLength,
locationManager: locationManager
)
.ignoresSafeArea()
}
)
.mapControls {
// TODO: make sure the user location stays on the map even if camera moves
MapUserLocationButton()
.onTapGesture {
locationManager.requestAuthorization()
@@ -59,12 +68,11 @@ struct ContentView: View {
if let userLocation = locationManager.location {
position = .camera(MapCamera(centerCoordinate: userLocation, distance: 1000))
}
Task {
await healthKitManager.requestAccess()
stepLength = await healthKitManager.getStepLength()
}
}
// Text("This is what's set: \(viewModel.test)")
// Button(action: {
// save(value: "te5t3")
// }, label: {Text("CLICK ME")})
}
func save(value: String) {

View File

@@ -0,0 +1,53 @@
//
// HealthKitManager.swift
// StepMap
//
// Created by Oliver Hnát on 27.11.2024.
//
import Foundation
import HealthKit
class HealthKitManager: ObservableObject {
let healthStore = HKHealthStore()
let allTypes: Set = [
HKQuantityType(.walkingSpeed),
HKQuantityType(.walkingStepLength),
]
var stepLength: Double?
func requestAccess() async {
do {
if HKHealthStore.isHealthDataAvailable() {
try await healthStore.requestAuthorization(toShare: Set(), read: allTypes)
}
} catch {
fatalError(
"Something went wrong while requesting healthKit permissions: \(error.localizedDescription)"
)
}
}
func getStepLength() async -> Double? {
if stepLength != nil {
return stepLength
}
let stepLengthType = HKQuantityType(.walkingStepLength)
let query = HKStatisticsQueryDescriptor(
predicate: HKSamplePredicate.quantitySample(type: stepLengthType),
options: .discreteAverage)
// maybe make an average of all the ones gotten in the last week?
do {
let results = try await query.result(for: healthStore)
stepLength = results?.averageQuantity()?.doubleValue(for: HKUnit.meter())
return stepLength
} catch {
fatalError(
"Something went wrong while getting step length from healthKit: \(error.localizedDescription)"
)
}
}
}

View File

@@ -5,6 +5,8 @@
// Created by Oliver Hnát on 23.11.2024.
//
import HealthKit
import HealthKitUI
import MapKit
import SwiftUI
@@ -12,6 +14,8 @@ struct SearchItemView: View {
var location: MKMapItem
@State var distance: CLLocationDistance?
@Binding var directions: [MKRoute]
@Binding var stepLength: Double?
@Binding var showSteps: Bool
@State var localDirections: [MKRoute] = []
var body: some View {
@@ -54,9 +58,13 @@ struct SearchItemView: View {
}
}
if distance != nil {
Button {
self.showSteps.toggle()
} label: {
Text("\(formatDistance(distance: distance!))")
}
}
}
Spacer()
}
}
@@ -68,6 +76,15 @@ struct SearchItemView: View {
}
func formatDistance(distance: CLLocationDistance) -> String {
let steps = distance * (stepLength ?? 0)
if steps != 0 && showSteps {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 0
formatter.numberStyle = .decimal
let number = NSNumber(value: steps)
return formatter.string(from: number)! + " steps"
// return String(format: "%.0f", steps)
}
let distanceFormatter = MKDistanceFormatter()
return distanceFormatter.string(fromDistance: distance)
}
@@ -86,8 +103,7 @@ struct SearchItemView: View {
let searchDirections = MKDirections(request: directionsRequest)
searchDirections.calculate { (response, error) in
guard let response = response else {
print(error ?? "")
print("Error while searching for directions")
print("Error while searching for directions: \(error?.localizedDescription ?? "")")
return
}
self.localDirections = response.routes

View File

@@ -9,9 +9,11 @@ import MapKit
import SwiftUI
struct SearchView: View {
@State private var query: String = "airport"
@State private var query: String = ""
@State private var locations: [MKMapItem] = []
@Binding var directions: [MKRoute]
@Binding var stepLength: Double?
@State var showSteps = true
var locationManager: LocationManager
var body: some View {
@@ -27,12 +29,12 @@ struct SearchView: View {
self.locations = []
}
}
.onAppear {
// TODO: delete this, it's for debug only
search(for: self.query)
}
// .onAppear {
// // TODO: delete this, it's for debug only
// search(for: self.query)
// }
.overlay {
HStack{
HStack {
Spacer()
Image(systemName: "multiply.circle.fill")
.foregroundStyle(.gray)
@@ -46,7 +48,9 @@ struct SearchView: View {
Spacer()
ScrollView {
ForEach(self.locations, id: \.identifier) { location in
SearchItemView(location: location, directions: $directions)
SearchItemView(
location: location, directions: $directions, stepLength: $stepLength,
showSteps: $showSteps)
}
}
}
@@ -65,7 +69,7 @@ struct SearchView: View {
let search = MKLocalSearch(request: searchRequest)
search.start { (response, error) in
guard let response = response else {
print("ERROR")
print(error)
return
}
var items: [MKMapItem] = []
@@ -73,8 +77,6 @@ struct SearchView: View {
if let name = item.name,
let location = item.placemark.location
{
print(
"\(name): \(location.coordinate.latitude),\(location.coordinate.longitude)")
items.append(item)
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.healthkit</key>
<true/>
</dict>
</plist>