Matt Ward
Matt Ward

Reputation: 1235

Accessing and manipulating array item in an EnvironmentObject

I have an EnvironmentObject that I'm using to generate a list

class ActivityViewModel: ObservableObject {
    @Published var Activities = [Activity]()
    
    init() {
        self.Activities = ActivityService.fetchActivities()
    }
}

struct Activity: Codable {
    var Id: UUID
    var Name: String
    var Records: [Record]
    
    init(Name:String) {
        self.Id = UUID.init()
        self.Name = Name
        self.Records = [Record]()
    }
}

When I'm using this in the view, I can use a ForEach to access each value in the Activities array.

My issue is when I use a NavigationLink to pass this Activity to another view, it's not then using the EnvironmentObject

My 'parent view' code:

ForEach(0..<activities.Activities.count, id: \.self) { activity in
    HStack {
        ActivityListItemView(activity: self.activities.Activities[activity])
    }
}

My child view code:

struct ActivityListItemView: View {
    let activity: Activity
    
    var body: some View {
        NavigationLink(destination: ActivityDetail(Activity: activity)) {
            HStack {
                VStack {
                    HStack {
                        Text(activity.Name)
                        Text("\(activity.Records.count) records")
                    }
                }
                
                Text(">")
            }
        }
        .buttonStyle(PlainButtonStyle())
    }
}

If I then update the viewModel, I need this to replicate into the child view. But I can't figure out how to make it use the EnvironmentObject, rather than just the variable I pass to the view.

Any help would be appreciated.

Edit: Added activity type

Upvotes: 1

Views: 142

Answers (1)

mahan
mahan

Reputation: 14975

This approach is similar to Apple's in this tutorials.

https://developer.apple.com/tutorials/swiftui/handling-user-input


Confirm to Identifiable and Equatable.


   struct Activity: Codable, Identifiable, Equatable {
       var id: UUID
       var name: String
   //    var Records: [Record]
       
       init(Name:String) {
           self.id = UUID()
           self.name = Name
   //        self.Records = [Record]()
       }
   }

Iterate over activity.activities and pass your view-model and activity to ActivityListItemView

   ForEach(viewModel.activities) { activity in
     HStack {
         ActivityListItemView(viewModel: viewModel, activity: activity)
     }
 }

In ActivityListItemView, find index of its activity

private var activityIndex: Int? {
     viewModel.activities.firstIndex(of: activity)
  }

Unwrap activityIndex and pass $viewModel.activities[index] to ActivityDetail

var body: some View {
      if let index = activityIndex {
          NavigationLink(destination: ActivityDetail(activity: $viewModel.activities[index])) {
              ...
          }
          ...
      }
  }

Use @Binding wrapper in ActivityDetail.


    struct ActivityDetail: View {
        @Binding var activity: Activity
        
        var body: some View {
            ...
        }
    }

A complete working exammple.

class ActivityViewModel: ObservableObject {
    @Published var activities = [Activity]()
    
    init() {
        self.activities = [Activity(Name: "A"), Activity(Name: "B"), Activity(Name: "C"), Activity(Name: "D"), Activity(Name: "E")]
    }
}

struct Activity: Codable, Identifiable, Equatable {
    var id: UUID
    var name: String
//    var Records: [Record]
    
    init(Name:String) {
        self.id = UUID()
        self.name = Name
//        self.Records = [Record]()
    }
}


struct ActivityView: View {
   @ObservedObject  var viewModel = ActivityViewModel()
    var body: some View {
        Button(action: {
            self.viewModel.activities.append(Activity(Name: "\(Date())"))
        }, label: {
            Text("Button")
        })
        ForEach(viewModel.activities) { activity in
            HStack {
                ActivityListItemView(viewModel: viewModel, activity: activity)
            }
        }

    }
}


struct ActivityListItemView: View {
    
    @ObservedObject var viewModel: ActivityViewModel
    
    let activity: Activity
    
    private var activityIndex: Int? {
        viewModel.activities.firstIndex(of: activity)
    }
    
    var body: some View {
        if let index = activityIndex {
            NavigationLink(destination: ActivityDetail(activity: $viewModel.activities[index])) {
                HStack {
                    VStack {
                        HStack {
                            Text(activity.name)
    //                        Text("\(activity.Records.count) records")
                        }
                    }
                    Text(">")
                }
            }
            .buttonStyle(PlainButtonStyle())
        }
    }
}


struct ActivityDetail: View {
    @Binding var activity: Activity
    
    var body: some View {
        Text("\(activity.name)")
    }
}

Upvotes: 1

Related Questions