I need to display a list of selections for the user to choose. I have examined Menu, .contextMenu(), and .popover(). While all three of these work fine, I cannot display what I need to show or I cannot style them to meet design needs. For example:
At this point it looks like popover is the most favorable option if I can set it up so the arrow is not displayed. From what I understand from the documentation only macOS is allowed to hide the arrow.
Is there a way to show .popover() without the arrow in iOS?
You could always build your own popover. The following techniques could be used:
ZStack
..matchedGeometryEffect
for positioning.Different anchors can be used to control exactly how the popover is positioned relative to a target. For example, to position the popover horizontally centered below a target, the target would use an anchor of .bottom
and the popover itself would use an anchor of .top
.
This shows it working:
struct ContentView: View {
enum PopoverTarget {
case text1
case text2
case text3
var anchorForPopover: UnitPoint {
switch self {
case .text1: .top
case .text2: .bottom
case .text3: .bottom
}
}
}
@State private var popoverTarget: PopoverTarget?
@Namespace private var nsPopover
@ViewBuilder
private var customPopover: some View {
if let popoverTarget {
Text("Popover for \(popoverTarget)")
.padding()
.foregroundStyle(.gray)
.background {
RoundedRectangle(cornerRadius: 10)
.fill(Color(white: 0.95))
.shadow(radius: 6)
}
.padding()
.matchedGeometryEffect(
id: popoverTarget,
in: nsPopover,
properties: .position,
anchor: popoverTarget.anchorForPopover,
isSource: false
)
}
}
private func showPopover(target: PopoverTarget) {
if popoverTarget != nil {
withAnimation {
popoverTarget = nil
} completion: {
popoverTarget = target
}
} else {
popoverTarget = target
}
}
var body: some View {
ZStack {
VStack {
Text("Text 1")
.padding()
.background(.blue)
.onTapGesture { showPopover(target: .text1) }
.matchedGeometryEffect(id: PopoverTarget.text1, in: nsPopover, anchor: .bottom)
.padding(.top, 50)
.padding(.leading, 100)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Text 2")
.padding()
.background(.orange)
.onTapGesture { showPopover(target: .text2) }
.matchedGeometryEffect(id: PopoverTarget.text2, in: nsPopover, anchor: .topLeading)
.padding(.top, 100)
.padding(.trailing, 40)
.frame(maxWidth: .infinity, alignment: .trailing)
Spacer()
Text("Text 3")
.padding()
.background(.green)
.onTapGesture { showPopover(target: .text3) }
.matchedGeometryEffect(id: PopoverTarget.text3, in: nsPopover, anchor: .top)
.padding(.bottom, 250)
}
customPopover
.transition(
.opacity.combined(with: .scale)
.animation(.bouncy(duration: 0.25, extraBounce: 0.2))
)
}
.foregroundStyle(.white)
.contentShape(Rectangle())
.onTapGesture {
popoverTarget = nil
}
}
}