Search code examples
swiftxcodeclassswiftuisingleton

SwiftUI: Singleton class not updating view


New to SwiftUI... I have the following simplified code. The intended functionality is to be able to navigate between View1() and View2(), using a singleton class to keep track of this navigation.

I suspect that maybe I needed to add @Published to my show_view_2 variable, but I want to keep it in App Storage. Also, I understand this isn't the best way to switch between views, but I only am using this method because it's a minimally reproduced example.

Why isn't the below code working, and how can I make it work?

class Player {
    static var player = Player()
    @AppStorage("show_view_2") var show_view_2: Bool = false
}

struct ContentView: View {  
    var body: some View {
        if(Player.player.show_view_2) {
            View2()
        } else {
            Text("Go to View2")
                .onTapGesture {
                    Player.player.show_view_2 = true
                }
        }
    }
}

struct View2: View {
    var body: some View {
        Text("Back to View1")
            .onTapGesture {
                Player.player.show_view_2 = false
            }
    }
}

Solution

  • For SwiftUI to know to update, Player should be an ObservableObject. Also, it'll need to be accessed using a property wrapper (either @StateObject or @ObservedObject) on the View:

    class Player : ObservableObject {
        static var player = Player()
        @AppStorage("show_view_2") var show_view_2: Bool = false
    }
    
    struct ContentView: View {
        @StateObject private var player = Player.player
        
        var body: some View {
            if player.show_view_2 {
                View2()
            } else {
                Text("Go to View2")
                    .onTapGesture {
                        player.show_view_2 = true
                    }
            }
        }
    }
    
    struct View2: View {
        @StateObject private var player = Player.player
        
        var body: some View {
            Text("Back to View1")
                .onTapGesture {
                    player.show_view_2 = false
                }
        }
    }
    

    In general, I'd recommend not using a singleton and instead passing an instance of the object explicitly between views -- this will be more testable and flexible over time:

    class Player : ObservableObject {
        @AppStorage("show_view_2") var show_view_2: Bool = false
    }
    
    struct ContentView: View {
        @StateObject private var player = Player()
        
        var body: some View {
            if player.show_view_2 {
                View2(player: player)
            } else {
                Text("Go to View2")
                    .onTapGesture {
                        player.show_view_2 = true
                    }
            }
        }
    }
    
    struct View2: View {
        @ObservedObject var player : Player
        
        var body: some View {
            Text("Back to View1")
                .onTapGesture {
                    player.show_view_2 = false
                }
        }
    }