feat(healthkit): get stepLength and calculate how many steps it takes to get somewhere
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
53
StepMap/Managers/HealthKitManager.swift
Normal file
53
StepMap/Managers/HealthKitManager.swift
Normal 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)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
8
StepMap/StepMap.entitlements
Normal file
8
StepMap/StepMap.entitlements
Normal 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>
|
||||
Reference in New Issue
Block a user