Search code examples
ioswatchkitswiftui

Reload SwiftUI View from a hosting controller


Is there a way to reload a swiftUI view from its hosting controller?

I have a WKHostingController with a swiftUI view for its body. When notification outside of the view gets fired, I need to update the view. If I try to change a @Binding or @State variable, I'm getting error

I can't update values outside of the view

But I can't find a refresh method.

Any ideas?

Hosting Controller:

class WorkoutListInterfaceController : WKHostingController<WorkoutListView> {
  override var body: WorkoutListView {
    WorkoutListView(host: self)
  }


  override func didAppear() {
    // update WorkoutListView!!!
  }

  func showExercises(forWorkout workout: Workout) {
    WKInterfaceController.reloadRootControllers(withNamesAndContexts: [(name: "ExerciseListInterfaceController", context: workout as AnyObject)])
  }

  func showAddWorkout() {
    presentController(withNamesAndContexts: [("AddWorkoutHostingController", context: "" as AnyObject)])
  }
}

SwiftUI View:

struct WorkoutListView : View {
  weak var host: WorkoutListInterfaceController?

  @State private var shouldPresentAddWorkout = false
  @State var workouts: [Workout] = Workout.all()

  init(host: WorkoutListInterfaceController) {
    self.host = host
  }

  var body: some View {
    return List {
      Section(header: Text("Workouts")
        .font(.system(size: 18))
        .fontWeight(.bold)
        .frame(height: 18))
      {
        ForEach(workouts) { workout in
          Button(action: {
            self.host?.showExercises(forWorkout: workout)
          }) {
            Text(workout.title)
          }
        }
      }

      Button(action: {
        self.host?.showAddWorkout()
      }) {
        Text("Add Workout")
          .fontWeight(.medium)
          .foregroundColor(.green)
          .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
      }
    }
    .sheet(isPresented: $shouldPresentAddWorkout, onDismiss: {}) {
      AddWorkoutView(host: AddWorkoutHostingController())
    }
    .contextMenu {
      Button(action: { self.host?.showAddWorkout() },
             label: {
              VStack {
                Image(systemName: "plus")
                Text("Add Workout")
              }
      })
    }
  }
}

WorkoutListInterfaceController's didAppear() gets called and I want to update WorkoutListView.

On onAppear(), swiftUI view modifier doesn't get called. I don't know if this is a bug or expected behavior, that's why I am defaulting to the didAppear() on the hosting controller to refresh the view.


Solution

  • Got this to work with using Combine and NotificationCenter by posting a notification when the presented view will dismiss:

    NotificationCenter.default.post(name: .updateWorkoutsNotification, object: workouts)
    

    And then subscribing to that notification in a WorkoutListView view model