swiftuipopover

Popover not showing, no matter what


I have this code:

import SwiftUI

struct ContentView: View {
  @State var show = false
  
  var tools = [Tool("document.on.document"), Tool("document.on.clipboard")]
  
  var body: some View {
    HStack{
      HStack {
        ForEach(tools,  id:\.id) { tool in
          Button(action: {
            print("toggle")
            show.toggle()
          }, label: {
            Image(systemName:tool.name)
              .imageScale(.large)
              .foregroundStyle(.black.gradient)
              .font(.system(size: 30))
          })
          .contentShape(Rectangle())
          .popover(isPresented: $show, arrowEdge: .top) {
            Color.red
              .frame(width:400, height:400)
              .onAppear{
                print("popover show")
              }
          }
        }

        
      }

      .padding()
    }
    .background(
      RoundedRectangle(cornerSize: CGSize(width: 50,height: 50))
        .fill(.red.opacity(0.5).gradient))
    
    .padding()
    
    
  }
}

#Preview {
  ContentView()
}

struct Tool: Identifiable, Equatable {
  let id = UUID()
  let name:String
  
  init(_ name: String) {
    self.name = name
  }
}

the popover does not show. I did everything.

Why is that?


Solution

  • You are using a single show state to control all of the popovers! Multiple popovers cannot be shown at the same time, so noting gets shown.

    It is technically possible to change the type of show to [Bool] and pass $show[0], $show[1] etc to isPresented, but it would be quite inconvenient to find the index of the tool and things like that.

    If the parent view doesn't need to know exactly which popovers are shown, you should extract the button into a separate view, and put the show state there.

    struct ToolButton: View {
        // move the @State here
        @State private var show = false
        let tool: Tool
    
        // pass anything else you need here...
        
        var body: some View {
            Button(action: {
                print("toggle")
                show.toggle()
            }, label: {
                Image(systemName: tool.name)
                    .imageScale(.large)
                    .foregroundStyle(.black.gradient)
                    .font(.system(size: 30))
            })
            .contentShape(Rectangle())
            .popover(isPresented: $show, arrowEdge: .top) {
                Color.red
                    .frame(width:400, height:400)
                    .presentationCompactAdaptation(.popover) // if you also want a popover on iPhones
                    .onAppear{
                        print("popover show")
                    }
            }
        }
    }
    

    Since the @State is in ToolButton, there will be as many states as there are ToolButton, which means one state to control each popover.

    ForEach(tools) { tool in
        ToolButton(tool: tool)
    }