Is there a better way to model a floating toolbar in SwiftUI that responds to @ EnvironmentObject changes without re-rendering the whole app?

Viewed 295

I am building an app using SwiftUI where the root view is a tab view with four tabs. One of the app features is the ability to play a podcast while you are browsing through the app. Once the audio begins playing, I want to show a "mini player" with simple start/stop controls, and also track the current progress.

To track progress, I have an @EnvironmentObject injected into the root view, which publishes play time updates, etc. so the mini player can respond. The problem I am finding is that each time the play time value changes, each tab is re-rendered (which is a bad thing) Here is some [simplified] code:

struct ContentView: View {
    @EnvironmentObject var playerManager: PlayerManager
    var body: some View {
        TabView(selection: self.$selection) {
            HomeView().tag(0)
            View2().tag(1)
            View3().tag(2)
            View4().tag(3)
        }
        .overlay(
              VStack {
                  if self.playerManager.showMiniPlayer {
                        MiniPlayerView()
                  }
              }
         )
    }
}

class PodcastPlayerManager: NSObject, ObservableObject {
    @Published private(set) var elapsedTime = ElapsedTime(progress: 0, duration: 0)

    // ... Things happen that cause `elapsedTime` to update
}

I put a log statement in the init() function of View2 and it was being called constantly...which is bad.

My question is whether or not this is the right/best way to model something like this using SwiftUI? I also considered injecting the PlayerManager into the MiniPlayer itself, but I was using a Geometry reader on ContentView to help position the floating MiniPlayer. showMiniPlayer is the only variable from the PlayerManger that I am concerned with in the ContentView... everything else is self-contained within the MiniPlayer View.

1 Answers

I have read somewhere you can use .id() to identify the view in such a way to prevent constant refresh. Something like:

@State private var vid = 0

then attach ".id(vid)" to either your TabView or the MiniPlayerView.

Let us know if this works.

Related