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 = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = StepMap/StepMap.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"StepMap/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"StepMap/Preview Content\"";
DEVELOPMENT_TEAM = SSJBLTMP95; DEVELOPMENT_TEAM = SSJBLTMP95;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = 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_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_NSLocationWhenInUseUsageDescription = "We need to access your location to set starting point of your route";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -280,12 +282,14 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = StepMap/StepMap.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"StepMap/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"StepMap/Preview Content\"";
DEVELOPMENT_TEAM = SSJBLTMP95; DEVELOPMENT_TEAM = SSJBLTMP95;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = 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_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_NSLocationWhenInUseUsageDescription = "We need to access your location to set starting point of your route";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;

View File

@@ -6,20 +6,25 @@
// //
import CoreLocation import CoreLocation
import HealthKitUI
import MapKit import MapKit
import SwiftUI import SwiftUI
struct ContentView: View { struct ContentView: View {
@ObservedObject var viewModel = ViewModel() @ObservedObject var viewModel = ViewModel()
@StateObject var locationManager = LocationManager() @StateObject var locationManager = LocationManager()
@StateObject var healthKitManager = HealthKitManager()
@State private var position = MapCameraPosition.automatic @State private var position = MapCameraPosition.automatic
@State private var showSearch: Bool = true @State private var showSearch: Bool = true
@State private var directions: [MKRoute] = [] @State private var directions: [MKRoute] = []
@State var healthKitAccess = false
@State var stepLength: Double?
// TODO: create a map // TODO: create a map
// Add navigation to the 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 // 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 "cancel" button that will hide the route
// add ability to hold on the map to place a mark (end goal) // 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 // Display the calculated distance and how long will it take by walking
@@ -38,11 +43,15 @@ struct ContentView: View {
.sheet( .sheet(
isPresented: $showSearch, isPresented: $showSearch,
content: { content: {
SearchView(directions: $directions, locationManager: locationManager) SearchView(
directions: $directions, stepLength: $stepLength,
locationManager: locationManager
)
.ignoresSafeArea() .ignoresSafeArea()
} }
) )
.mapControls { .mapControls {
// TODO: make sure the user location stays on the map even if camera moves
MapUserLocationButton() MapUserLocationButton()
.onTapGesture { .onTapGesture {
locationManager.requestAuthorization() locationManager.requestAuthorization()
@@ -59,12 +68,11 @@ struct ContentView: View {
if let userLocation = locationManager.location { if let userLocation = locationManager.location {
position = .camera(MapCamera(centerCoordinate: userLocation, distance: 1000)) 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) { 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. // Created by Oliver Hnát on 23.11.2024.
// //
import HealthKit
import HealthKitUI
import MapKit import MapKit
import SwiftUI import SwiftUI
@@ -12,6 +14,8 @@ struct SearchItemView: View {
var location: MKMapItem var location: MKMapItem
@State var distance: CLLocationDistance? @State var distance: CLLocationDistance?
@Binding var directions: [MKRoute] @Binding var directions: [MKRoute]
@Binding var stepLength: Double?
@Binding var showSteps: Bool
@State var localDirections: [MKRoute] = [] @State var localDirections: [MKRoute] = []
var body: some View { var body: some View {
@@ -54,9 +58,13 @@ struct SearchItemView: View {
} }
} }
if distance != nil { if distance != nil {
Button {
self.showSteps.toggle()
} label: {
Text("\(formatDistance(distance: distance!))") Text("\(formatDistance(distance: distance!))")
} }
} }
}
Spacer() Spacer()
} }
} }
@@ -68,6 +76,15 @@ struct SearchItemView: View {
} }
func formatDistance(distance: CLLocationDistance) -> String { 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() let distanceFormatter = MKDistanceFormatter()
return distanceFormatter.string(fromDistance: distance) return distanceFormatter.string(fromDistance: distance)
} }
@@ -86,8 +103,7 @@ struct SearchItemView: View {
let searchDirections = MKDirections(request: directionsRequest) let searchDirections = MKDirections(request: directionsRequest)
searchDirections.calculate { (response, error) in searchDirections.calculate { (response, error) in
guard let response = response else { guard let response = response else {
print(error ?? "") print("Error while searching for directions: \(error?.localizedDescription ?? "")")
print("Error while searching for directions")
return return
} }
self.localDirections = response.routes self.localDirections = response.routes

View File

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