Search code examples
swiftui

SwiftUI - .onTapGesture strange behaviour


I'm trying to create an app that displays recent activities - I'm having trouble with implementing a functionality that displays a detail sheet for the selected activity. The problem is that when I start the app and I try to open any activity detail I get a blank sheet. What is even stranger, if I try to tap it any number of times, it still presents an empty sheet. BUT, here comes the weird part, when I tap any other item/activity, it functions as intended and I always get the right detail after that no matter which activity I tap.

I tried to use a DispatchQueue in order to ensure that the activity is actually stored in the proper variable before presenting it but that didn't help. I think I'm missing some crucial info but have no idea what it is.

The part where I try to do that starts with ForEach(lastWeeksActivities)

Thanks for any input!

//
//  ActivityOverviewView.swift
//  MyAdventure
//
//  Created by Tomáš Dušek on 29.01.2025.
//

import SwiftUI
import SwiftData
import HealthKit


struct ActivityOverviewView: View {
    @EnvironmentObject var manager: HealthManager
    @State var activityToPresent: Activity?
    @State private var isActivityDetailPresented = false
    @State private var lastWeekActivities: [Activity] = []
    @State private var vm = ActivityOverviewViewmodel()
    @State private var todaySteps: Int = 0
    @State private var todayStepGoal: Int = 10000
    @State private var todayCalories: Int = 0
    @State private var todayCaloriesGoal: Int = 500
    @State private var todayActiveMinutes: Int = 0
    @State private var todayActiveMinutesGoal: Int = 60
    @State private var todayDistance: Double = 0
    @State private var todayDistaneGoal: Double = 5
    
    
    var body: some View {
        ScrollView{
                VStack{
                    HStack{
                        Text("Today's Overview")
                            .font(.title.bold())
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding([.top, .horizontal])
                    }
                    
                    VStack{
                        HStack{
                            
                            CircleViewStyle(color: .blue, icon: "shoeprints.fill", goal: todayStepGoal, progress: todaySteps, unit: "steps")
                            CircleViewStyle(color: .green, icon: "figure.run", goal: todayActiveMinutesGoal, progress: todayActiveMinutes, unit: "min")
                        }
                        HStack {
                            CircleViewStyle(color: .orange, icon: "flame.fill", goal: todayCaloriesGoal, progress: todayCalories, unit: "kcal")
                            CircleViewStyle(color: .yellow, icon: "questionmark", goal: 0, progress: 0, unit: "")
                        }

                    }
                    .padding()
         
                }
                VStack{
                    Text("Recent Activities")
                        .font(.title2.bold())
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(.horizontal)
                    
                    VStack {
                        if lastWeekActivities.isEmpty {
                   
                            ContentUnavailableView(
                                "No Recent Activities",
                                systemImage: "figure.walk",
                                description: Text("Start your first adventure!")
                            )
                            .padding()
            
                            
                        } else {
                            VStack {
                                ForEach(lastWeekActivities) { activity in
                                    HStack{
                                        ActivityNavigationLinkView(activity: activity)
                                            .padding(.vertical, 8)
                                            .padding(.horizontal)
                                        Spacer()
                                        Image(systemName: "chevron.right")
                                            .padding(.horizontal)
                                    }
                                    .contentShape(Rectangle())
                                    .onTapGesture {
                                        activityToPresent = activity
                                        isActivityDetailPresented = true
                                    }
                                }
                                .background(.thinMaterial)
                                .cornerRadius(12)
                                .padding(.horizontal)
                                .padding(.bottom, 5)
                                
                            }
                            .sheet(isPresented: $isActivityDetailPresented) {
                                if let activity = activityToPresent {
                                    ActivityDetailView(activity: activity)
                                        .presentationDetents([.medium])
                                }
                            }
                        }
                    }
                }
        }
        .refreshable {
            reloadData()
        }
        .onAppear {
            reloadData()
        }
    }
    
    private func reloadData() {
        Task {
            await vm.loadActivities()
            lastWeekActivities = vm.lastWeekActivities
            todaySteps = try await manager.fetchTodaySteps()
            todayCalories = try await manager.fetchTodayCalories()
            todayActiveMinutes = vm.calculateActiveMinutes()
        }
    }

    
    struct CircleViewStyle: View {
        let color: Color
        let icon: String
        let goal: Int
        let progress: Int
        var percantage: Double {
            return Double(progress) / Double(goal)
        }
        let unit: String
        
        var body: some View {
            ZStack {
                Circle()
                    .stroke(color.opacity(0.3), lineWidth: 10)
                Circle()
                    .trim(from: 0, to: percantage)
                    .stroke(color, style: StrokeStyle(lineWidth: 10, lineCap: .round))
                    .rotationEffect(Angle(degrees: -90))
                    .shadow(radius: 5)
                
                VStack{
                    Image(systemName: ("\(icon)"))
                        .font(.system(size: 30, weight: .bold, design: .default))
                        .padding(.bottom, 5)
                    Text("\(progress) / \(goal) \(unit)")
                        .font(.system(size: 14, weight: .medium, design: .default))
                        .padding(.horizontal)
                        .lineLimit(2)
                        .multilineTextAlignment(.center)
                        .minimumScaleFactor(0.5)
                }
            }
            .padding()
            
        }
    }
    
}

#Preview {
    ActivityOverviewView()
        .environmentObject(HealthManager())
}


Solution

  • try

    .sheet(item: $activityToPresent) {...}
    

    to "Presents a sheet using the given item as a data source for the sheet’s content".

    See sheet