How to use Popover modifier in SwiftUI | SwiftUI Bootcamp #69
A popover is a small floating panel that appears anchored to the button that triggered it — perfect for context-sensitive options, quick feedback forms, or any secondary interaction that doesn't need a full-screen sheet. On iPhone, .presentationCompactAdaptation(.popover) ensures it behaves like a true popover instead of adapting to a sheet.
What You'll Learn
- How to present a popover using the
.popover(isPresented:attachmentAnchor:content:)modifier - How
attachmentAnchorcontrols where the popover arrow points - How
.presentationCompactAdaptation(.popover)keeps the popover style on compact (iPhone) screens
Mental Model
Think of a popover like a speech bubble in a comic. The bubble contains text (your content), and there's an arrow pointing to exactly who is speaking (the anchor). On iPad, popovers naturally look like speech bubbles attached to the button that triggered them. On iPhone — which has a compact screen — SwiftUI normally adapts popovers to sheets because sheets feel more natural there. .presentationCompactAdaptation(.popover) says "no, keep the speech bubble, even on small screens."
Detailed Explanation
.popover(isPresented:attachmentAnchor:arrowEdge:content:) presents a floating panel when the isPresented binding becomes true. Unlike a sheet, a popover is dismissed by tapping outside it (no swipe gesture). The popover is logically attached to the view it's applied to.
attachmentAnchor determines where the popover arrow appears. .point(.top) anchors the arrow to the top center of the trigger button — the popover appears above it. .point(.bottom) puts the arrow at the bottom — the popover appears below. The available UnitPoint values include .top, .bottom, .leading, .trailing, .topLeading, etc.
On iPhone (compact horizontal size class), SwiftUI historically adapted .popover to a full-screen sheet because popovers don't fit well on small screens. .presentationCompactAdaptation(.popover) — available since iOS 16.4 — overrides this adaptation and forces true popover behavior even on iPhone. Use this when your popover content is genuinely small and contextual, not when it contains a lot of information that would be better in a sheet.
The content closure of the popover is a regular SwiftUI view. Here a ScrollView containing a VStack of feedback options demonstrates a typical pattern: a small menu of choices that the user taps once and the popover dismisses.
Code Structure
NativePopoverBootcamp.swift contains a feedback button positioned at the bottom of the screen. Tapping it reveals a popover anchored above the button with three feedback options. The list of options is in a @State array, making it easy to add, remove, or reorder items. The dividers between options are conditionally rendered to avoid a trailing separator.
Complete Code
NativePopoverBootcamp.swift
import SwiftUI
struct NativePopoverBootcamp: View {
@State private var showPopover: Bool = false
@State private var feedbackOptions: [String] = [
"Very good 🥳",
"Average 🙂",
"Very bad 😡"
]
var body: some View {
ZStack {
Color.gray.ignoresSafeArea() // Background to visually contrast the popover
VStack {
Spacer() // Pushes the button to the bottom of the screen
// The popover modifier is applied to the button — the popover anchors to this view
Button("Provide feedback?") {
showPopover.toggle()
}
.padding(20)
.background(Color.yellow)
.popover(isPresented: $showPopover, attachmentAnchor: .point(.top), content: {
// Popover appears above the button because the attachment point is .top
ScrollView {
VStack(alignment: .leading, spacing: 12, content: {
ForEach(feedbackOptions, id: \.self) { option in
Button(option) {
// Selecting an option would go here — e.g., dismiss and record feedback
}
// Adds a divider between options but not after the last one
if option != feedbackOptions.last {
Divider()
}
}
})
.padding(20)
}
// Forces popover style on iPhone instead of adapting to a sheet
.presentationCompactAdaptation(.popover)
})
}
}
}
}
struct NativePopoverBootcamp_Previews: PreviewProvider {
static var previews: some View {
NativePopoverBootcamp()
}
}Code Walkthrough
@State private var showPopover: Bool = false— The Boolean that drives the popover's visibility. Setting it totruepresents the popover; the system sets it back tofalsewhen the user taps outside.@State private var feedbackOptions: [String]— The options array is@Stateso it could be modified at runtime (adding, removing options). Using an array also lets you useForEachwith a single source of truth rather than hard-coding each button..popover(isPresented: $showPopover, attachmentAnchor: .point(.top))— Applied directly to theButton. This means the popover arrow points to this button specifically.attachmentAnchor: .point(.top)puts the arrow at the top of the button, so the popover floats above it.ScrollViewinside the popover — The popover content can be any view hierarchy.ScrollViewis used here so that if the options list grew long, users could still access all options. TheVStackinside has.leadingalignment for a clean left-aligned list.if option != feedbackOptions.last { Divider() }— This idiom renders dividers between all items except after the last one. This is a common SwiftUI pattern for inter-item separators without a trailing separator. Note: comparing with.lastworks because strings areEquatable..presentationCompactAdaptation(.popover)— Applied on the popover's content view (not on the presenter). This is the modern iOS 16.4+ way to keep popover style on iPhone. Without it, the popover becomes a sheet on compact screens.
Common Mistakes
Mistake: Applying .presentationCompactAdaptation to the presenting view instead of the content view.presentationCompactAdaptation(.popover) must be placed on the view inside the popover closure, not on the button that shows it. It's a presentation modifier like .presentationDetents — it belongs on the presented content.
Mistake: Using .popover for large content on iPhone
Even with .presentationCompactAdaptation(.popover), a popover with lots of content is hard to use on iPhone's small screen. If your content has more than 5–6 items or needs significant height, present a .sheet with .presentationDetents([.medium]) instead — it's more thumb-friendly.
Mistake: Attaching the .popover modifier to a container instead of the trigger control
If you attach .popover to a VStack instead of the specific Button inside it, the popover's arrow points to the VStack's frame — not the button. Always attach .popover to the exact view you want the arrow to point at.
Key Takeaways
.popovercreates a floating panel anchored to the view it's applied to;attachmentAnchorcontrols where the arrow points- On iPhone, SwiftUI adapts popovers to sheets by default — use
.presentationCompactAdaptation(.popover)(iOS 16.4+) to force true popover behavior - Tap outside the popover to dismiss it — there's no swipe gesture or dismiss button needed
Last updated: June 27, 2026