diff options
| -rw-r--r-- | iCUrHealth/ContentView.swift | 270 | ||||
| -rw-r--r-- | iCUrHealth/HomeView.swift | 154 | ||||
| -rw-r--r-- | iCUrHealth/UserCharts.swift | 6 | ||||
| -rw-r--r-- | iCUrHealth/WorkoutMapView.swift | 140 | 
4 files changed, 299 insertions, 271 deletions
| diff --git a/iCUrHealth/ContentView.swift b/iCUrHealth/ContentView.swift index 78cf965..9a85785 100644 --- a/iCUrHealth/ContentView.swift +++ b/iCUrHealth/ContentView.swift @@ -30,139 +30,6 @@ let allTypes: Set = [      HKCategoryType(.sleepAnalysis)  ] -import HealthKit -import CoreLocation - -struct WorkoutRoute { -    var coordinates: [CLLocationCoordinate2D] -} - -class WorkoutViewModel: ObservableObject { -    @Published var workout: HKWorkout? -    @Published var workoutRoute: WorkoutRoute? { -        didSet { -            workoutRouteCoordinates = workoutRoute?.coordinates ?? [] -        } -    } -    @Published var workoutRouteCoordinates: [CLLocationCoordinate2D] = [] -    private var healthStore = HKHealthStore() - -    func fetchAndProcessWorkoutRoute() { -        let workoutPredicate = HKQuery.predicateForWorkouts(with: .downhillSkiing) -        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - -        let workoutQuery = HKSampleQuery(sampleType: .workoutType(), predicate: workoutPredicate, limit: 1, sortDescriptors: [sortDescriptor]) { (query, samples, error) in -            guard let workouts = samples as? [HKWorkout], error == nil else { -                // Handle the error here. -                return -            } -             -            // Process the fetched workouts here. -            for workout in workouts { -                let routePredicate = HKQuery.predicateForObjects(from: workout) -                let routeQuery = HKAnchoredObjectQuery(type: HKSeriesType.workoutRoute(), predicate: routePredicate, anchor: nil, limit: HKObjectQueryNoLimit) { (query, routeSamples, deletedObjects, anchor, error) in -                    guard let routeSamples = routeSamples as? [HKWorkoutRoute], error == nil else { -                        // Handle the error here -                        return -                    } - -                    for routeSample in routeSamples { -                        let workoutRouteQuery = HKWorkoutRouteQuery(route: routeSample) { (query, locationsOrNil, done, errorOrNil) in -                            guard let locations = locationsOrNil, errorOrNil == nil else { -                                // Handle error -                                return -                            } - -                            let allCoordinates = locations.map { $0.coordinate } - -                            // Once all coordinates are fetched, update the published property -                            DispatchQueue.main.async { -                                self.workoutRoute = WorkoutRoute(coordinates: allCoordinates) -                                self.workout = workout -                            } - -                            if done { -                                // Finish processing as needed -                            } -                        } -                        self.healthStore.execute(workoutRouteQuery) -                    } -                } -                self.healthStore.execute(routeQuery) -            } -        } - -        self.healthStore.execute(workoutQuery) -    } -} - -struct IdentifiableCoordinate: Identifiable { -    let id = UUID() -    var coordinate: CLLocationCoordinate2D -} - -struct MapView: View { -    var route: WorkoutRoute - -    // Convert coordinates to identifiable coordinates -    var identifiableCoordinates: [IdentifiableCoordinate] { -        route.coordinates.map { IdentifiableCoordinate(coordinate: $0) } -    } - -    var body: some View { -        Map(coordinateRegion: .constant(regionForRoute()), -            showsUserLocation: false, -            userTrackingMode: .none, -            annotationItems: identifiableCoordinates) { item in -                MapPin(coordinate: item.coordinate, tint: .blue) -            } -            .overlay( -                MapOverlay(coordinates: route.coordinates) -                    .stroke(Color.blue, lineWidth: 3) -            ) -            .cornerRadius(10) // Optional: Adds rounded corners to the map -    } - -    func regionForRoute() -> MKCoordinateRegion { -//        guard let firstCoordinate = route.coordinates.first else { -//            return MKCoordinateRegion() -//        } -         -        let count = route.coordinates.count / 2 -        guard let firstCoordinate = route.coordinates.prefix(count).last else { -            return MKCoordinateRegion() -        } -         -        return MKCoordinateRegion(center: firstCoordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)) -    } -} - -struct MapOverlay: Shape { -    var coordinates: [CLLocationCoordinate2D] - -    func path(in rect: CGRect) -> Path { -        var path = Path() - -        guard let firstCoordinate = coordinates.first else { -            return path -        } - -        let mapRect = MKMapRect.world -        let firstPoint = MKMapPoint(firstCoordinate) -        let startPoint = CGPoint(x: (firstPoint.x / mapRect.size.width) * rect.size.width, y: (1 - firstPoint.y / mapRect.size.height) * rect.size.height) -        path.move(to: startPoint) - -        for coordinate in coordinates.dropFirst() { -            let mapPoint = MKMapPoint(coordinate) -            let point = CGPoint(x: (mapPoint.x / mapRect.size.width) * rect.size.width, y: (1 - mapPoint.y / mapRect.size.height) * rect.size.height) -            path.addLine(to: point) -        } - -        return path -    } -} - -  struct ContentView: View {      @State var authenticated = false      @State var trigger = false @@ -175,71 +42,7 @@ struct ContentView: View {      var body: some View {              TabView{ -                NavigationView { -                    List { -                        HStack { -                            Text("Steps") -                            Chart(data) { -                                BarMark(x: .value("Date", $0.dateInterval), -                                        y: .value("Count", $0.data) -                                ) -                                 -                            } -                        }.frame(maxHeight: 100) -                            .navigationTitle("iCUrHealth") -                        Button(action: { -                            Task { -                                try await fetchStepCountData() -                            } -                        }) -                        { -                            Text("Exp Function") -                        } -                        if !viewModel.workoutRouteCoordinates.isEmpty { -                            VStack { -                                HStack { -                                    Text("Latest Downhill Skiing Workout").font(.callout) -                                    Spacer() -                                } -                                MapView(route: viewModel.workoutRoute!) -                                    .frame(height: 300) -                                HStack { -                                    VStack { -                                        Text("Top Speed") -                                        Text("\(viewModel.workout!.totalDistance!)") -                                    } -                                } -                            } -                        } else { -                            Text("Fetching workout route...") -                                .onAppear { -                                    if HKHealthStore.isHealthDataAvailable() { -                                        trigger.toggle() -                                    } -                                    viewModel.fetchAndProcessWorkoutRoute() -                                } -                                .healthDataAccessRequest(store: healthStore, -                                                         readTypes: allTypes, -                                                         trigger: trigger) { result in -                                    switch result { -                                         -                                    case .success(_): -                                        authenticated = true -                                        Task { -                                            try await fetchStepCountData() -                                        } -                                    case .failure(let error): -                                        // Handle the error here. -                                        fatalError("*** An error occurred while requesting authentication: \(error) ***") -                                    } -                                } -                        } -                         -                    }.listRowSpacing(10) -                } -                .padding() -                .navigationTitle("iCUrHealth") -                 +                HomeView()                  .tabItem {                      Image(systemName: "gear")                      Text("Home") @@ -257,75 +60,4 @@ struct ContentView: View {              }      } -     -    private func experimentWithSkiWorkout() async throws { -        let workoutPredicate = HKQuery.predicateForWorkouts(with: .downhillSkiing) -        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - -        let workoutQuery = HKSampleQuery(sampleType: .workoutType(), predicate: workoutPredicate, limit: 3, sortDescriptors: [sortDescriptor]) { (query, samples, error) in -             -            guard let workouts = samples as? [HKWorkout], error == nil else { -                // Handle the error here. -                return -            } -             -            // Process the fetched workouts here. -            for workout in workouts { -                print(workout) -            } -        } -    } -     -    private func fetchStepCountData() async throws { -        let calendar = Calendar(identifier: .gregorian) -        let today = calendar.startOfDay(for: Date()) - - -        guard let endDate = calendar.date(byAdding: .day, value: 1, to: today) else { -            fatalError("*** Unable to calculate the end time ***") -        } - - -        guard let startDate = calendar.date(byAdding: .day, value: -29, to: endDate) else { -            fatalError("*** Unable to calculate the start time ***") -        } - - -        let thisWeek = HKQuery.predicateForSamples(withStart: startDate, end: endDate) - - -        // Create the query descriptor. -        let stepType = HKQuantityType(.stepCount) -        let stepsThisWeek = HKSamplePredicate.quantitySample(type: stepType, predicate:thisWeek) -        let everyDay = DateComponents(day:1) - - -        let sumOfStepsQuery = HKStatisticsCollectionQueryDescriptor( -            predicate: stepsThisWeek, -            options: .cumulativeSum, -            anchorDate: endDate, -            intervalComponents: everyDay) - - -        let stepCounts = try await sumOfStepsQuery.result(for: self.healthStore) -         -        var dailyData: [chartData] = [] -         -        stepCounts.enumerateStatistics(from: startDate, to: endDate) { stats, _ in -            if let quantity = stats.sumQuantity() { -                //print(quantity, stats.startDate) -                dailyData.append( -                    chartData(tag: "activity", dateInterval: stats.startDate, data: quantity.doubleValue(for: HKUnit.count())) -                ) -            } else { -            } -             -        } -         -        data = dailyData -    } -} - -#Preview { -    ContentView()  } diff --git a/iCUrHealth/HomeView.swift b/iCUrHealth/HomeView.swift new file mode 100644 index 0000000..c29758a --- /dev/null +++ b/iCUrHealth/HomeView.swift @@ -0,0 +1,154 @@ +// +//  HomeView.swift +//  iCUrHealth +// +//  Created by Navan Chauhan on 2/10/24. +// + +import SwiftUI +import HealthKit +import HealthKitUI +import Charts + +struct HomeView: View { +    @State var authenticated = false +    @State var trigger = false +     +    @StateObject private var viewModel = WorkoutViewModel() +     +    let healthStore = HKHealthStore() +     +    @State private var data: [chartData] = [] +    var body: some View { +        NavigationView { +            List { +                HStack { +                    Text("Steps") +                    Chart(data) { +                        BarMark(x: .value("Date", $0.dateInterval), +                                y: .value("Count", $0.data) +                        ) +                         +                    } +                }.frame(maxHeight: 100) +                     +                Button(action: { +                    Task { +                        try await fetchStepCountData() +                    } +                }) +                { +                    Text("Exp Function") +                } +                if !viewModel.workoutRouteCoordinates.isEmpty { +                    VStack { +                        HStack { +                            Text("Latest Downhill Skiing Workout").font(.callout) +                            Spacer() +                        } +                        MapView(route: viewModel.workoutRoute!) +                            .frame(height: 300) +                        HStack { +                            VStack { +                                Text("Top Speed") +                                Text("\(viewModel.workout!.totalDistance!)") +                            } +                        } +                    } +                } else { +                    Text("Fetching workout route...") +                        .onAppear { +                            if HKHealthStore.isHealthDataAvailable() { +                                trigger.toggle() +                            } +                            viewModel.fetchAndProcessWorkoutRoute() +                        } +                        .healthDataAccessRequest(store: healthStore, +                                                 readTypes: allTypes, +                                                 trigger: trigger) { result in +                            switch result { +                                 +                            case .success(_): +                                authenticated = true +                                Task { +                                    try await fetchStepCountData() +                                } +                            case .failure(let error): +                                // Handle the error here. +                                fatalError("*** An error occurred while requesting authentication: \(error) ***") +                            } +                        } +                } +                 +            }.listRowSpacing(10) +                .navigationTitle("iCUrHealth") +        } +    } +     +    private func experimentWithSkiWorkout() async throws { +        let workoutPredicate = HKQuery.predicateForWorkouts(with: .downhillSkiing) +        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) + +        let workoutQuery = HKSampleQuery(sampleType: .workoutType(), predicate: workoutPredicate, limit: 3, sortDescriptors: [sortDescriptor]) { (query, samples, error) in +             +            guard let workouts = samples as? [HKWorkout], error == nil else { +                // Handle the error here. +                return +            } +             +            // Process the fetched workouts here. +            for workout in workouts { +                print(workout) +            } +        } +    } +     +    private func fetchStepCountData() async throws { +        let calendar = Calendar(identifier: .gregorian) +        let today = calendar.startOfDay(for: Date()) + + +        guard let endDate = calendar.date(byAdding: .day, value: 1, to: today) else { +            fatalError("*** Unable to calculate the end time ***") +        } + + +        guard let startDate = calendar.date(byAdding: .day, value: -29, to: endDate) else { +            fatalError("*** Unable to calculate the start time ***") +        } + + +        let thisWeek = HKQuery.predicateForSamples(withStart: startDate, end: endDate) + + +        // Create the query descriptor. +        let stepType = HKQuantityType(.stepCount) +        let stepsThisWeek = HKSamplePredicate.quantitySample(type: stepType, predicate:thisWeek) +        let everyDay = DateComponents(day:1) + + +        let sumOfStepsQuery = HKStatisticsCollectionQueryDescriptor( +            predicate: stepsThisWeek, +            options: .cumulativeSum, +            anchorDate: endDate, +            intervalComponents: everyDay) + + +        let stepCounts = try await sumOfStepsQuery.result(for: self.healthStore) +         +        var dailyData: [chartData] = [] +         +        stepCounts.enumerateStatistics(from: startDate, to: endDate) { stats, _ in +            if let quantity = stats.sumQuantity() { +                //print(quantity, stats.startDate) +                dailyData.append( +                    chartData(tag: "activity", dateInterval: stats.startDate, data: quantity.doubleValue(for: HKUnit.count())) +                ) +            } else { +            } +             +        } +         +        data = dailyData +    } +} diff --git a/iCUrHealth/UserCharts.swift b/iCUrHealth/UserCharts.swift index c44b84c..0fdf9d5 100644 --- a/iCUrHealth/UserCharts.swift +++ b/iCUrHealth/UserCharts.swift @@ -43,7 +43,9 @@ func getAverage(healthData: [HealthData]) -> Double {  }  struct UserCharts: View { -    @State private var healthData: [HealthData] = [] +//    var charts: [userChart] +    @State var charts: [userChart] = [] +    @State var healthData: [HealthData] = []      var body: some View {          VStack{              Text("Step Count").font(.title) @@ -81,7 +83,7 @@ struct UserCharts: View {                      }                  }              }.frame(height: 250) -        }.onAppear{ +        }.onAppear {              let healthDataFetcher = HealthDataFetcher()              Task {                  self.healthData = try await healthDataFetcher.fetchAndProcessHealthData() diff --git a/iCUrHealth/WorkoutMapView.swift b/iCUrHealth/WorkoutMapView.swift new file mode 100644 index 0000000..48617a2 --- /dev/null +++ b/iCUrHealth/WorkoutMapView.swift @@ -0,0 +1,140 @@ +// +//  WorkoutMapView.swift +//  iCUrHealth +// +//  Created by Navan Chauhan on 2/10/24. +// + +import SwiftUI +import HealthKit +import CoreLocation +import MapKit + +struct WorkoutRoute { +    var coordinates: [CLLocationCoordinate2D] +} + +class WorkoutViewModel: ObservableObject { +    @Published var workout: HKWorkout? +    @Published var workoutRoute: WorkoutRoute? { +        didSet { +            workoutRouteCoordinates = workoutRoute?.coordinates ?? [] +        } +    } +    @Published var workoutRouteCoordinates: [CLLocationCoordinate2D] = [] +    private var healthStore = HKHealthStore() + +    func fetchAndProcessWorkoutRoute() { +        let workoutPredicate = HKQuery.predicateForWorkouts(with: .downhillSkiing) +        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) + +        let workoutQuery = HKSampleQuery(sampleType: .workoutType(), predicate: workoutPredicate, limit: 1, sortDescriptors: [sortDescriptor]) { (query, samples, error) in +            guard let workouts = samples as? [HKWorkout], error == nil else { +                // Handle the error here. +                return +            } +             +            // Process the fetched workouts here. +            for workout in workouts { +                let routePredicate = HKQuery.predicateForObjects(from: workout) +                let routeQuery = HKAnchoredObjectQuery(type: HKSeriesType.workoutRoute(), predicate: routePredicate, anchor: nil, limit: HKObjectQueryNoLimit) { (query, routeSamples, deletedObjects, anchor, error) in +                    guard let routeSamples = routeSamples as? [HKWorkoutRoute], error == nil else { +                        // Handle the error here +                        return +                    } + +                    for routeSample in routeSamples { +                        let workoutRouteQuery = HKWorkoutRouteQuery(route: routeSample) { (query, locationsOrNil, done, errorOrNil) in +                            guard let locations = locationsOrNil, errorOrNil == nil else { +                                // Handle error +                                return +                            } + +                            let allCoordinates = locations.map { $0.coordinate } + +                            // Once all coordinates are fetched, update the published property +                            DispatchQueue.main.async { +                                self.workoutRoute = WorkoutRoute(coordinates: allCoordinates) +                                self.workout = workout +                            } + +                            if done { +                                // Finish processing as needed +                            } +                        } +                        self.healthStore.execute(workoutRouteQuery) +                    } +                } +                self.healthStore.execute(routeQuery) +            } +        } + +        self.healthStore.execute(workoutQuery) +    } +} + +struct IdentifiableCoordinate: Identifiable { +    let id = UUID() +    var coordinate: CLLocationCoordinate2D +} + +struct MapView: View { +    var route: WorkoutRoute + +    // Convert coordinates to identifiable coordinates +    var identifiableCoordinates: [IdentifiableCoordinate] { +        route.coordinates.map { IdentifiableCoordinate(coordinate: $0) } +    } + +    var body: some View { +        Map(coordinateRegion: .constant(regionForRoute()), +            showsUserLocation: false, +            userTrackingMode: .none, +            annotationItems: identifiableCoordinates) { item in +                MapPin(coordinate: item.coordinate, tint: .blue) +            } +            .overlay( +                MapOverlay(coordinates: route.coordinates) +                    .stroke(Color.blue, lineWidth: 3) +            ) +            .cornerRadius(10) // Optional: Adds rounded corners to the map +    } + +    func regionForRoute() -> MKCoordinateRegion { +//        guard let firstCoordinate = route.coordinates.first else { +//            return MKCoordinateRegion() +//        } +         +        let count = route.coordinates.count / 2 +        guard let firstCoordinate = route.coordinates.prefix(count).last else { +            return MKCoordinateRegion() +        } +         +        return MKCoordinateRegion(center: firstCoordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)) +    } +} + +struct MapOverlay: Shape { +    var coordinates: [CLLocationCoordinate2D] + +    func path(in rect: CGRect) -> Path { +        var path = Path() + +        guard let firstCoordinate = coordinates.first else { +            return path +        } + +        let mapRect = MKMapRect.world +        let firstPoint = MKMapPoint(firstCoordinate) +        let startPoint = CGPoint(x: (firstPoint.x / mapRect.size.width) * rect.size.width, y: (1 - firstPoint.y / mapRect.size.height) * rect.size.height) +        path.move(to: startPoint) + +        for coordinate in coordinates.dropFirst() { +            let mapPoint = MKMapPoint(coordinate) +            let point = CGPoint(x: (mapPoint.x / mapRect.size.width) * rect.size.width, y: (1 - mapPoint.y / mapRect.size.height) * rect.size.height) +            path.addLine(to: point) +        } + +        return path +    } +} | 
