I'm trying to get a .popover
to work with a Map > Marker but can't seem to get it. I've tried to go about this by getting the coordinates and presenting a .popover
based on that, but I can't get it to work. Simply putting the .popover
modifier on Image("Logo")
didn't seem to work either. Here is my current code
import SwiftUI
import MapKit
struct MapView: View {
@State private var region: MKCoordinateRegion
var libraries: [Library]
@State private var selectedLibrary: Library?
init(libraries: [Library]) {
if let firstLibrary = libraries.first {
_region = State(initialValue: MKCoordinateRegion(
center: firstLibrary.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
))
} else {
_region = State(initialValue: MKCoordinateRegion())
}
self.libraries = libraries
}
var body: some View {
ZStack {
Map {
ForEach(libraries) { library in
Marker(coordinate: library.coordinate) {
Image("Logo")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
.tint(.purple)
}
}
.edgesIgnoringSafeArea(.all)
GeometryReader { geometry in
ForEach(libraries) { library in
Button(action: {
self.selectedLibrary = library
}, label: {
Color.clear
})
.frame(width: 44, height: 44)
.position(self.coordinateToPoint(library.coordinate, in: geometry))
.popover(isPresented: Binding<Bool>(
get: { self.selectedLibrary == library },
set: { if !$0 { self.selectedLibrary = nil } }
)) {
Text("Annotation details here")
.padding()
}
}
}
}
}
private func coordinateToPoint(_ coordinate: CLLocationCoordinate2D, in geometry: GeometryProxy) -> CGPoint {
let mapWidthDegrees = region.span.longitudeDelta
let mapHeightDegrees = region.span.latitudeDelta
let widthPerDegree = geometry.size.width / mapWidthDegrees
let heightPerDegree = geometry.size.height / mapHeightDegrees
let xCoordinate = (coordinate.longitude - region.center.longitude + mapWidthDegrees / 2) * widthPerDegree
let yCoordinate = (region.center.latitude - coordinate.latitude + mapHeightDegrees / 2) * heightPerDegree
return CGPoint(x: xCoordinate, y: yCoordinate)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(libraries: LibraryData.libraries)
}
}
And here is my LibraryData and Library for reference
import CoreLocation
struct Library: Identifiable, Equatable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
static func == (lhs: Library, rhs: Library) -> Bool {
lhs.id == rhs.id &&
lhs.coordinate.latitude == rhs.coordinate.latitude &&
lhs.coordinate.longitude == rhs.coordinate.longitude
}
}
struct LibraryData {
static let libraries = [
Library(coordinate: CLLocationCoordinate2D(latitude: 37.33182, longitude: -122.03118)),
Library(coordinate: CLLocationCoordinate2D(latitude: 37.34182, longitude: -122.03218)),
Library(coordinate: CLLocationCoordinate2D(latitude: 37.34182, longitude: -122.04118))
]
}
When I tap the marker inside the map nothing happens. Any help would be appreciated. Thanks in advance.
You could try a different approach using Annotation
instead of Marker
and the .popover()
.
The example code shows how to tap on any Annotation
and popup a custom view.
struct ContentView: View {
var body: some View {
MapView(libraries: LibraryData.libraries)
}
}
struct MapView: View {
@State private var region: MKCoordinateRegion
var libraries: [Library]
@State private var selectedLibrary: Library?
// for testing
@State private var cameraPosition: MapCameraPosition = .camera(
MapCamera(centerCoordinate: CLLocationCoordinate2D(latitude: 37.33182, longitude: -122.03118), distance: 8000.0, heading: 0, pitch: 0)
)
init(libraries: [Library]) {
if let firstLibrary = libraries.first {
_region = State(initialValue: MKCoordinateRegion(
center: firstLibrary.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
))
} else {
_region = State(initialValue: MKCoordinateRegion())
}
self.libraries = libraries
}
@State private var toggler = false // <-- here to toggle the popup view
var body: some View {
Map(position: $cameraPosition) {
ForEach(libraries) { library in
Annotation("", coordinate: library.coordinate) {
ZStack {
Image(systemName: "mappin.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.onTapGesture {
selectedLibrary = library
toggler.toggle()
}
.foregroundStyle(.white, .purple)
if selectedLibrary == library && toggler {
PopupView(library: selectedLibrary)
} else {
EmptyView()
}
}
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
// just for testing, adjust the looks as needed
struct PopupView: View {
@State var library: Library?
var body: some View {
VStack {
if let lib = library {
Text("Poping: \(String(lib.id.uuidString.prefix(4)))")
} else {
Text("no data")
}
}
.foregroundStyle(.purple)
.frame(width: 120, height: 100)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
.offset(x: 0, y: -70)
}
}
EDIT-1:
you can use a .popover(...)
if this is really what you want.
I feel it does not looks great on ios devices, but looks good on mac/macCatalyst and iPad, and the Button
works.
struct MapView: View {
@State private var region: MKCoordinateRegion
var libraries: [Library]
// for testing
@State private var cameraPosition: MapCameraPosition = .camera(
MapCamera(centerCoordinate: CLLocationCoordinate2D(latitude: 37.33182, longitude: -122.03118), distance: 8000.0, heading: 0, pitch: 0)
)
init(libraries: [Library]) {
if let firstLibrary = libraries.first {
_region = State(initialValue: MKCoordinateRegion(
center: firstLibrary.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
))
} else {
_region = State(initialValue: MKCoordinateRegion())
}
self.libraries = libraries
}
var body: some View {
Map(position: $cameraPosition) {
ForEach(libraries) { library in
Annotation("", coordinate: library.coordinate) {
MakerView(library: library)
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
struct MakerView: View {
@State var library: Library
@State private var showPopover = false
var body: some View {
Image(systemName: "mappin.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.onTapGesture {
showPopover = true
}
.foregroundStyle(.white, .purple)
.popover(isPresented: $showPopover) {
Text("Annotation details here")
Button("click me"){
print("----> Button clicked")
}.buttonStyle(.bordered)
}
}
}