Home > Enterprise >  Why doesn't SwiftUI reflect a @State property value in a toolbar Menu?
Why doesn't SwiftUI reflect a @State property value in a toolbar Menu?

Time:08-19

Testing this piece of code with Xcode 14 beta 5. Everything is working properly (sorting items with an animation, plus saving the selected sortOrder in UserDefaults).

However the if-else condition in the Menu's label seems to be ignored and the label is not updated. Please do you know why?

struct ContentView: View {
    enum SortOrder: String, CaseIterable, Identifiable {
        case forward
        case reverse
        
        var id: Self {
            self
        }
        
        static let `default`: Self = .forward
        
        var label: String {
            switch self {
            case .forward: return "Sort (forward)"
            case .reverse: return "Sort (reverse)"
            }
        }
    }
    
    @AppStorage("sortOrder") var sortOrder: SortOrder = .default {
        didSet {
            sort()
        }
    }
    
    
    @State private var items = ["foo", "bar", "baz"]
    
    @ToolbarContentBuilder
    var toolbar: some ToolbarContent {
        ToolbarItem {
            Menu {
                ForEach(SortOrder.allCases) { sortOrder in
                    Button {
                        withAnimation { self.sortOrder = sortOrder }
                    } label: {
                        // FIXME: doesn't reflect sortOrder value
                        if sortOrder == self.sortOrder {
                            Label(sortOrder.label, systemImage: "checkmark")
                        } else {
                            Text(sortOrder.label)
                        }
                    }
                }
            } label: {
                Label("Actions", systemImage: "ellipsis.circle")
            }
        }
    }
    
    var body: some View {
        NavigationStack {
            List(items, id: \.self) { item in
                Text(item)
            }
            .navigationTitle("Test")
            .toolbar { toolbar }
            .onAppear(perform: sort)
        }
    }
    
    func sort() {
        switch sortOrder {
        case .forward:
            items.sort(by: <)
        case .reverse:
            items.sort(by: >)
        }
    }
}

CodePudding user response:

The if-else in your ToolbarItem is working correctly. The problem here is that .labelStyle in a toolbar are defaulted to .titleOnly.

To display the label with both icon and title, you would need to add .labelStyle(.titleAndIcon).

CodePudding user response:

It is selected but Menu is not updated, assuming in toolbar it should be persistent.

A possible workaround is to force-rebuild menu on sort change, like

Menu {

   // ...

} label: {
    Label("Actions", systemImage: "ellipsis.circle")
}
.id(self.sortOrder)  // << here !!

Tested with Xcode 14b5 / iOS 16

  • Related