SwiftUI 试图从列表中删除对象但无法在不可变值上使用变异成员:是一个“让”常量

Posted

技术标签:

【中文标题】SwiftUI 试图从列表中删除对象但无法在不可变值上使用变异成员:是一个“让”常量【英文标题】:SwiftUI trying to remove object from a list but getting Cannot use mutating member on immutable value: is a 'let' constant 【发布时间】:2021-01-29 22:21:58 【问题描述】:

我将整个视图发布到Github Gist,因为我认为您需要查看所有内容。问题是在滑动删除时,我正在删除 HealthKit 锻炼,但我无法删除列表中的行。第 132 行是我试图从列表中删除锻炼的地方。取消注释,编译器说“不能在不可变值上使用变异成员:'workoutMonth' 是一个 'let' 常量”,但它是 var 及其 @State

import SwiftUI
import HealthKit

struct MonthsWorkoutsListView: View 
    
    
    @Environment(\.colorScheme) var colorScheme
    @EnvironmentObject var trackerDataStore: TrackerDataStore
    @State var workoutMonths = [WorkoutMonth]()
    
    @State private var showingDeleteFailOrSuccessAlert = false
     @State private var deleteFailOrSuccessAlert = Alert(title: Text(""))
    
    
    var body: some View 
        VStack 
            if workoutMonths.count == 0 
                Text("No workouts logged yet.  Open the Athlytic App on your Apple Watch to start and save a new workout.")
                    .multilineTextAlignment(.center)
                    .padding(.horizontal)
             else 
                List 
                    ForEach(workoutMonths)  workoutMonth in
                        Section(header: Text(getFormattedYearMonth(workoutMonth: workoutMonth))) 
                            ForEach(workoutMonth.trackerWorkouts)  workout in
                                NavigationButton(
                                    action: 
                                        trackerDataStore.selectedWorkout = workout //this line is needed so that the image  at the top of WorkoutDetailHeaderCard gets updated,  otherwise the view loads before the async called and you end up with the  image of the last selected workout
                                        trackerDataStore.loadDataForSelectedWorkout(selectedWorkoutToBeUpdated: workout)
                                    ,
                                    destination: 
                                        WorkoutDetailView().environmentObject(trackerDataStore)
                                    ,
                                    workoutRow:  WorkoutRow(trackerWorkout: workout) 
                                )
                            
                            .onDelete  (offSets) in
                                deleteWorkout(offSets: offSets, workoutMonth: workoutMonth)
                            
                        
                    
                    
                
                .alert(isPresented: $showingDeleteFailOrSuccessAlert ) 
                    deleteFailOrSuccessAlert
                    
                
                
                .navigationBarTitle(Text("Workouts"))
            
        
        .onAppear 
            workoutMonths = buildWorkoutsIntoYearMonths(workouts: trackerDataStore.workouts)
        
    
    
    func buildWorkoutsIntoYearMonths(workouts: [TrackerWorkout]) -> [WorkoutMonth] 
        
        var data = [YearMonth: [TrackerWorkout]]()
        var tempWorkoutMonths = [WorkoutMonth]()
        
        for workout in workouts 
            //print("workout UUID = \(workout.uuid) for workout dated \(workout.startDate)")
            let yearMonth = YearMonth(date: workout.startDate)
            
            //Build a year  month  with an empty array of hockeyTracker workouts
            var yearMonthWorkouts = data[yearMonth, default: [TrackerWorkout]()]
            yearMonthWorkouts.append(workout)
            data[yearMonth] = yearMonthWorkouts
            
        
        
        //Order YearMonths into newest > oldest
        let sortedDataKeys = data.keys.sorted(by: >)
        
        //Build yearMonths into Struct which is needed for SwiftUI
        for yearMonth in sortedDataKeys  
            let newYearMonth = WorkoutMonth(yearMonth: yearMonth, trackerWorkouts: data[yearMonth]!)
            tempWorkoutMonths.append(newYearMonth)
        
        return tempWorkoutMonths
    
    
    
    func getFormattedYearMonth(workoutMonth: WorkoutMonth) -> String 
        
        var monthAsString = ""
        
        switch (workoutMonth.yearMonth.month) 
        case 1: monthAsString = "January"
        case 2: monthAsString = "February"
        case 3: monthAsString = "March"
        case 4: monthAsString = "April"
        case 5: monthAsString = "May"
        case 6: monthAsString = "June"
        case 7: monthAsString = "July"
        case 8: monthAsString = "August"
        case 9: monthAsString = "September"
        case 10: monthAsString = "October"
        case 11: monthAsString = "November"
        case 12: monthAsString = "December"
            
        default:
            print("Default")
        
        
        let yearAsString = (workoutMonth.yearMonth.year.description)
        let yearWithoutFirst2Char = yearAsString.dropFirst(2)
        
        let formattedSection = "\(monthAsString) '\(yearWithoutFirst2Char)"
        
        return formattedSection
    
    
    private func deleteWorkout(offSets: IndexSet, workoutMonth: WorkoutMonth) 
        
        for offset in offSets 
            if let unwrappedHKWorkout = workoutMonth.trackerWorkouts[offset].hkWorkout 
                
                guard unwrappedHKWorkout.sourceRevision.source.name == "Athlytic" else 
                    print("sourceName isn't Athlytic it is \(unwrappedHKWorkout.sourceRevision.source.name)")
                    showingDeleteFailOrSuccessAlert.toggle()
                    deleteFailOrSuccessAlert = Alert(title: Text("Delete Error"), message: Text("Apple only permits us to delete a workout that was logged with the Athlytic Watch App.  To delete this workout you need to delete it in the Apple Health or Fitness App."), dismissButton: .default(Text("ok")))
                    return
                    
                
                
                HKHealthStore().delete(unwrappedHKWorkout)  (success, error) in
                    DispatchQueue.main.async 
                        if success 
                            print("success workout deleted")
                           //workoutMonth.trackerWorkouts.remove(at: offset)
                            showingDeleteFailOrSuccessAlert.toggle()
                            deleteFailOrSuccessAlert = Alert(title: Text("workout successfully deleted"))
                            
                         else if let unwrappedError = error 
                            print("error could not delete workout because \(unwrappedError)")
                            showingDeleteFailOrSuccessAlert.toggle()
                            deleteFailOrSuccessAlert = Alert(title: Text("Delete Error"), message: Text("We were unable to delete this workout due to \(unwrappedError.localizedDescription)"), dismissButton: .default(Text("ok")))
                            
                        
                    
                    
                
            
            
        
    


//struct MonthsWorkoutsList_Previews: PreviewProvider 
//    static var previews: some View 
//        MonthsWorkoutsListView()
//    
//



struct YearMonth: Comparable, Hashable 
    
    let year: Int
    let month: Int
    
    init(year: Int, month: Int) 
        self.year = year
        self.month = month
    
    
    init(date: Date) 
        let comps = Calendar.current.dateComponents([.year, .month], from: date)
        self.year = comps.year!
        self.month = comps.month!
    
    
    static func < (lhs: YearMonth, rhs: YearMonth) -> Bool 
        if lhs.year != rhs.year 
            return lhs.year < rhs.year
         else 
            return lhs.month < rhs.month
        
    


struct WorkoutMonth: Identifiable 
    var id = UUID()
    var yearMonth: YearMonth
    var trackerWorkouts: [TrackerWorkout]

【问题讨论】:

请不要发布指向您的代码的链接。我已更新您的问题以包含您的代码。 【参考方案1】:

您正在尝试修改 workoutMonth,这是一个传入函数的参数,使其默认为 let 变量。

你需要做更多这样的事情(请注意,这是未经测试的,可能不精确,因为你没有提供一个简单的项目来玩,我只是从要点中阅读,但它应该至少让你开始):

self.workoutMonths = self.workoutMonths.map  wm in 
  if wm.id == workoutMonth.id 
   var copiedMonth = wm
   copiedMonth.trackerWorkouts.remove(at: offset)
   return copiedMonth
   else 
   return wm
  

【讨论】:

以上是关于SwiftUI 试图从列表中删除对象但无法在不可变值上使用变异成员:是一个“让”常量的主要内容,如果未能解决你的问题,请参考以下文章

无法正确地将此嵌套对象设置在不可变记录中

无法在 SwiftUI 中使用 ForEach 和 CoreData 将数据正确传递给模态演示

从 SwiftUI 中的列表中删除绑定

在 PySpotify 中,试图在不知道曲目在播放列表中的位置的情况下从播放列表中删除曲目

如何从 SwiftUI 和 Realm 中另一个列表中的对象中添加和删除列表中的对象

SwiftUI + Realm:从列表中删除一行会导致异常“RLMException”,原因:“对象已被删除或失效。”