skip to Main Content

I’m trying to learn SwiftUI, but i can’t seem to get my view to update. I want my WorkoutsView to refresh with the newly added workout when the user presses the "Add" button:

WorkoutTrackerApp:

@main
struct WorkoutTrackerApp: App {
    var body: some Scene {
        WindowGroup {
            WorkoutTrackerView()
        }
    }
}

extension WorkoutTrackerApp {
    struct WorkoutTrackerView: View {
        @StateObject var workoutService = WorkoutService.instance
        
        var body: some View {
            NavigationView {
                WorkoutsView { $workout in
                    NavigationLink(destination: WorkoutView(workout: $workout)){
                        Text(workout.title)
                    }
                }
                .toolbar {
                    Button("Add") {
                        workoutService.addNewWorkout()
                    }
                }
                .navigationTitle("Workouts")
            }
            .environmentObject(workoutService)
        }
    }
}

WorkoutsView:

import Foundation
import SwiftUI

struct WorkoutsView<Wrapper>: View where Wrapper: View {
    @EnvironmentObject var workoutService: WorkoutService
    @StateObject var viewModel: ViewModel
    let workoutWrapper: (Binding<Workout>) -> Wrapper
    
    init(_ viewModel: ViewModel = .init(), workoutWrapper: @escaping (Binding<Workout>) -> Wrapper) {
        _viewModel = StateObject(wrappedValue: viewModel)
        self.workoutWrapper = workoutWrapper
    }
    
    var body: some View {
        List {
            Section(header: Text("All Workouts")) {
                ForEach($viewModel.workouts) { $workout in
                    workoutWrapper($workout)
                }
            }
        }
        .onAppear {
            viewModel.workoutService = self.workoutService
            viewModel.getWorkouts()
        }
    }
}

extension WorkoutsView {
    class ViewModel: ObservableObject {
        @Published var workouts = [Workout]()
        var workoutService: WorkoutService?
        
        func getWorkouts() {
            workoutService?.getWorkouts { workouts in
                self.workouts = workouts
            }
        }
    }
}

WorkoutService:

import Foundation

class WorkoutService: ObservableObject {
    static let instance = WorkoutService()
    @Published var workouts = [Workout]()
    
    private init() {
        for i in 0...5 {
            let workout = Workout(id: i, title: "Workout (i)", exercises: [])
            workouts.append(workout)
        }
    }
    
    func getWorkouts(completion: @escaping ([Workout]) -> Void) {
        DispatchQueue.main.async {
            completion(self.workouts)
        }
    }
    
    func addNewWorkout() {
        let newWorkout = Workout(title: "New Workout")
        workouts = workouts + [newWorkout]
    }
}

The .onAppear in WorkoutsView only gets called once – when the view gets initialised for the first time. I want it to also get triggered when workoutService.addNewWorkout() gets called.

FYI: The WorkoutService is a ‘mock’ service, in the future i want to call an API there.

2

Answers


  1. Chosen as BEST ANSWER

    Figured it out, changed the body of WorkoutsView to this:

        var body: some View {
            List {
                Section(header: Text("All Workouts")) {
                    ForEach($viewModel.workouts) { $workout in
                        workoutWrapper($workout)
                    }
                }
            }
            .onAppear {
                viewModel.workoutService = self.workoutService
                viewModel.getWorkouts()
            }
            .onReceive(workoutService.objectWillChange) {
                viewModel.getWorkouts()
            }
        }
    

    Now the workouts list gets refreshed when workoutService publisher emits. The solution involved using the .onReceive to do something when the WorkoutService changes.


  2. .onReceive(workoutService.objectWillChange) {
      addButton()
    }
    

    It’s good working

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search