admin管理员组文章数量:1356815
I am working with a SwiftUI List
on macOS, and I have a popover view that I need to show from each row. I have an implementation that works but it shows the popover from one fixed point on the row. I would like to be able to show the popover from where the mouse was clicked in the row, with the arrow pointing to the mouse location.
This is my current implementation:
import SwiftUI
struct TestPopoverView: View {
let items = ["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"]
var body: some View {
List(items, id: \.self) { item in
ListRowView(item: item)
}
}
}
struct ListRowView: View {
let item: String
@State private var showPopover = false
var body: some View {
HStack {
Text(item)
.padding()
Spacer()
}
.contentShape(Rectangle()) // Ensures the entire row is tappable
.onTapGesture {
showPopover = true
}
.popover(isPresented: $showPopover, attachmentAnchor: .rect(.bounds), arrowEdge: .bottom) {
PopoverContentView(item: item)
}
}
}
struct PopoverContentView: View {
let item: String
var body: some View {
VStack {
Text("Selected: \(item)")
.padding()
}
.frame(width: 200, height: 100)
}
}
#Preview {
TestPopoverView()
.frame(width: 300, height: 350)
}
How do I get the mouse location and the popover showing from that precise location? I don't understand how to specify the attachmentAnchor
to do this, while working inside a List
.
I am working with a SwiftUI List
on macOS, and I have a popover view that I need to show from each row. I have an implementation that works but it shows the popover from one fixed point on the row. I would like to be able to show the popover from where the mouse was clicked in the row, with the arrow pointing to the mouse location.
This is my current implementation:
import SwiftUI
struct TestPopoverView: View {
let items = ["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"]
var body: some View {
List(items, id: \.self) { item in
ListRowView(item: item)
}
}
}
struct ListRowView: View {
let item: String
@State private var showPopover = false
var body: some View {
HStack {
Text(item)
.padding()
Spacer()
}
.contentShape(Rectangle()) // Ensures the entire row is tappable
.onTapGesture {
showPopover = true
}
.popover(isPresented: $showPopover, attachmentAnchor: .rect(.bounds), arrowEdge: .bottom) {
PopoverContentView(item: item)
}
}
}
struct PopoverContentView: View {
let item: String
var body: some View {
VStack {
Text("Selected: \(item)")
.padding()
}
.frame(width: 200, height: 100)
}
}
#Preview {
TestPopoverView()
.frame(width: 300, height: 350)
}
How do I get the mouse location and the popover showing from that precise location? I don't understand how to specify the attachmentAnchor
to do this, while working inside a List
.
- Did my answer help solving your issue? – soundflix Commented 2 days ago
1 Answer
Reset to default 0Unfortunately, I don't know if there is an easy answer.
But it's makeable and the flow is like this:
PopoverAttachmentAnchor
takes either a rect or a point. The type of this point isUnitPoint
.
A normalized 2D point in a view’s coordinate space.
This means we need the location where the mouse clicks and the size of the view.
The size we can get with
GeometryReader
, I use a custom modifier calledreadSize
to hide the details from the view's body.The local coordinates of the mouse pointer we get with the
onContinuousHover
modifier.We store both values as they change.
Finally, when the mouse clicks, we calculate the current click point in
UnitPoint
s to use it in thepopover
modifier.
This is the full code:
import SwiftUI
struct TestPopoverView: View {
let items = ["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"]
var body: some View {
List(items, id: \.self) { item in
ListRowView(item: item)
}
}
}
struct ListRowView: View {
let item: String
@State private var showPopover = false
@State private var size: CGSize = .zero
@State private var mouseLocation: CGPoint?
func normalize(point: CGPoint?, in size: CGSize)-> UnitPoint? {
guard let point else {
return nil
}
return UnitPoint(x: point.x / size.width, y: point.y / size.height)
}
@State var clickPoint: UnitPoint = .center
var body: some View {
HStack {
Text(item)
.padding()
Spacer()
}
.border(Color.yellow)
.contentShape(Rectangle()) // Ensures the entire row is tappable
.readSize { size in
self.size = size
}
.onContinuousHover { phase in
switch phase {
case .active(let location):
mouseLocation = location
case .ended:
break
}
}
.onTapGesture {
if let point = normalize(point: mouseLocation, in: size) {
clickPoint = point
showPopover = true
}
}
.popover(isPresented: $showPopover, attachmentAnchor: .point(clickPoint), arrowEdge: .bottom) {
PopoverContentView(item: item)
}
}
}
extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background {
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
}
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
If you are targeting macOS 13+, you could replace readSize
with onGeometryChange
.
.onGeometryChange(for: CGSize.self) { proxy in
proxy.size
} action: {
self.size = $0
}
本文标签: macosSwiftUI Show popover at mouse click locationStack Overflow
版权声明:本文标题:macos - SwiftUI: Show popover at mouse click location - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744070764a2585839.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论