1

How to prevent the button background darkening after closing a popover? The attached gif shows the button becoming darker before it resets to glass.

Gif showing button animation issue

Here is the code for the button and the popover:

struct CustomMenuView<Label: View, Content: View>: View {
    
    @ViewBuilder var label: Label
    @ViewBuilder var content: Content
    @State private var isExpanded: Bool = false
    
    @Namespace private var namespace
    
    var body: some View {
        Button {
            isExpanded.toggle()
        } label: {
            label.matchedTransitionSource(id: "MENUCONTENT", in: namespace)
        }
        .popover(isPresented: $isExpanded) {
            content
            .navigationTransition(.zoom(sourceID: "MENUCONTENT", in: namespace)).presentationCompactAdaptation(.popover)
        }
    }
}

And here is where I use the button:

struct SelectorMenu: View {
    
    @Binding var selectedLeague: League
    
    private let leagues: [League] = [.nfl, .mlb, .nba]
    
    var body: some View {
        CustomMenuView() {
            buttonLabel
        } content: {
            menuView
        }
    }

    @ViewBuilder
    private var buttonLabel: some View {
        Text("Button")
    }
    
    @ViewBuilder
    private var menuView: some View {
        VStack {
            Text("MENU ITEM 1")
            Divider()
            Text("MENU ITEM 2")
            Divider()
            Text("MENU ITEM 3")
            Divider()
            Text("MENU ITEM 4")
            Divider()
            Text("MENU ITEM 5")
        }
        .padding()
    }
}

I have tried changing the background color of the popover like this:

struct CustomMenuView<Label: View, Content: View>: View {
    
    @ViewBuilder var label: Label
    @ViewBuilder var content: Content
    @State private var isExpanded: Bool = false
    
    @Namespace private var namespace
    
    var body: some View {
        Button {
            isExpanded.toggle()
        } label: {
            label.matchedTransitionSource(id: "MENUCONTENT", in: namespace)
        }
        .popover(isPresented: $isExpanded) {
            content
            .navigationTransition(.zoom(sourceID: "MENUCONTENT", in: namespace)).presentationCompactAdaptation(.popover)
            .background(.red)
        }
    }
}

This changes the background color of the popover once its fully expanded, but the closing animation still uses the darker material as shown in the video above.

I also tried making the presentation background clear but that didn't do anything:

struct CustomMenuView<Label: View, Content: View>: View {
    
    @ViewBuilder var label: Label
    @ViewBuilder var content: Content
    @State private var isExpanded: Bool = false
    
    @Namespace private var namespace
    
    var body: some View {
        Button {
            isExpanded.toggle()
        } label: {
            label.matchedTransitionSource(id: "MENUCONTENT", in: namespace)
        }
        .popover(isPresented: $isExpanded) {
            content
            .navigationTransition(.zoom(sourceID: "MENUCONTENT", in: namespace)).presentationCompactAdaptation(.popover)
            .presentationBackground(.clear)
        }
    }
}
5
  • Have you considered using a native Menu? With iOS 26, the menu label morphs into the menu popover without any extra effort needed. Commented Oct 14 at 18:39
  • Yeah thats what I originally used, but the Menu high jacks the styling of all the child buttons so I have very little freedom. I'm trying to create a popover menu with small buttons and avatars as well as a selection menu. Commented Oct 14 at 19:47
  • 1
    Well then you could consider a custom popover too, that might give you more control over shadow effects, animation and positioning. See this answer for an example implementation. Commented Oct 14 at 20:12
  • Btw, if the button is being shown as a ToolbarItem then you don't need the .matchedTransitionSource or .navigationTransition, which means you don't need the Namespace either. You can get rid of them all and it still works the same. So the undesired darkening effect is coupled with the presentation of the .popover itself. Using a custom popover would be a way to solve. Commented Oct 15 at 8:51
  • 1
    Yeah this is coming from the toolbar and the animation when I was using the Menu out of the box was pretty great, it just didn't let me create anything other than a button with an icon and a title. I'm trying to create something that looks like this: cdsassets.apple.com/live/7WUAS350/images/ios/…. Really annoying that Apple seemingly breaks it own design rules in its own apps. Commented Oct 15 at 14:53

1 Answer 1

0

179


Without seeing what's happening behind-the-scenes, I don't have enough time to fully debug this... But basically whaat I figured out is: As so long as the view inside the popover has a minimum frame of 179 width & height, the problem goes away. Luckily this seems inline with your aspirations...

Tested true on iPhone 15 device & iPhone 17 simulator.
iOS 26
-Props to @Benzy Neez for pointing out the code redundancies in the comments.

@available( iOS 16.4 , * )
@main
struct SQLiteApp: App {
  var body: some Scene {
    WindowGroup {
      NavigationStack {
        Color ( #colorLiteral(red: 0.1960784346, green: 0.3411764801, blue: 0.1019607857, alpha: 1) )
          .ignoresSafeArea()
          .toolbar { TopBar() }
          .navigationTitle ( "SPORTS!" )
          .labelsHidden()
      }
    }
  }
}

@available( iOS 16.4 , * )
struct TopBar: ToolbarContent {
  @State private var isExpanded: Bool = false
  
  var body: some ToolbarContent {
    ToolbarItem ( id: "Toggle" , placement: .topBarLeading ) {
      Toggle ( "Hidden" , isOn: $isExpanded )
        .padding()
        .toggleStyle ( .switch )
    }
    
    ToolbarItem ( id: "Menu" , placement: .primaryAction ) {
      Button { isExpanded.toggle() } label: { Text ( "Button" ) }
        .popover ( isPresented: $isExpanded ) {
          MenuView()
            .presentationCompactAdaptation ( .popover )
        }
    }
  }
}

struct MenuView: View {
  @State var currentLeague: League = .NBA
  var body: some View {
    VStack ( alignment: .leading ) {
      League.picker ( selection: $currentLeague )
      ForEach ( 1..<6 ) { Text ( "  MENU ITEM \( $0.description )" ) }
    }
    .padding()
    .frame ( width: 179 , height: 179 , alignment: .topLeading ) // <-- HERE
//    .frame ( width: 178 , height: 178 , alignment: .topLeading ) // <-- NOT HERE
  }
}

enum League: String , CaseIterable , Hashable {
  case NFL , NBA , MLB
  static func picker ( selection: Binding < Self > ) -> some View {
    Picker ( "Hidden" , selection: selection ) { ForEach ( League.allCases , id: \.self ) { Text ( $0.rawValue ) } }
      .pickerStyle ( .segmented )
  }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.